#!/usr/bin/python3
import argparse
import logging
import tkinter as tk
from tkinter import TclError, filedialog, ttk
from pathlib import Path
from shared_libs.gitea import GiteaUpdate
from shared_libs.common_tools import (
    LogConfig,
    ConfigManager,
    ThemeManager,
    LxTools,
    Tooltip,
)
import sys
from file_and_dir_ensure import prepare_app_environment
import webbrowser


class LogViewer(tk.Tk):
    def __init__(self, modul_name):
        super().__init__()

        self.my_tool_tip = None
        self.modul_name = modul_name  # Save the module name
        # from here the calls must be made with the module name
        _ = modul_name.AppConfig.setup_translations()

        self.x_width = modul_name.AppConfig.UI_CONFIG["window_size"][0]
        self.y_height = modul_name.AppConfig.UI_CONFIG["window_size"][1]
        # Set the window size
        self.geometry(f"{self.x_width}x{self.y_height}")
        self.minsize(
            modul_name.AppConfig.UI_CONFIG["window_size"][0],
            modul_name.AppConfig.UI_CONFIG["window_size"][1],
        )
        self.title(modul_name.AppConfig.UI_CONFIG["window_title2"])
        self.tk.call(
            "source", f"{modul_name.AppConfig.SYSTEM_PATHS['tcl_path']}/water.tcl"
        )
        ConfigManager.init(modul_name.AppConfig.SETTINGS_FILE)
        theme = ConfigManager.get("theme")
        ThemeManager.change_theme(self, theme)
        LxTools.center_window_cross_platform(self, self.x_width, self.y_height)
        self.createWidgets(_)
        self.load_file(_, modul_name=modul_name)
        self.log_icon = tk.PhotoImage(file=modul_name.AppConfig.IMAGE_PATHS["icon_log"])
        self.iconphoto(True, self.log_icon)
        self.grid_rowconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)

        # StringVar-Variables initialization
        self.tooltip_state = tk.BooleanVar()
        # Get value from configuration
        state = ConfigManager.get("tooltips")
        # NOTE: ConfigManager.get("tooltips") can return either a boolean value or a string,
        # depending on whether the value was loaded from the file (bool) or the default value is used (string).
        # The expression 'lines[5].strip() == "True"' in ConfigManager.load() converts the string to a boolean.
        # Convert to boolean and set
        if isinstance(state, bool):
            # If it's already a boolean, use directly
            self.tooltip_state.set(state)
        else:
            # If it's a string or something else
            self.tooltip_state.set(str(state) == "True")

        self.tooltip_label = (
            tk.StringVar()
        )  # StringVar-Variable for tooltip label for view Disabled/Enabled
        self.tooltip_update_label(modul_name, _)
        self.update_label = tk.StringVar()  # StringVar-Variable for update label
        self.update_tooltip = (
            tk.StringVar()
        )  # StringVar-Variable for update tooltip please not remove!
        self.update_foreground = tk.StringVar(value="red")

        # Frame for Menu
        self.menu_frame = ttk.Frame(self)
        self.menu_frame.configure(relief="flat")
        if "'logview_app_config'" in f"{modul_name}".split():
            self.menu_frame.grid(column=0, row=0, columnspan=4, sticky=tk.NSEW)

        # App Menu
        self.version_lb = ttk.Label(self.menu_frame, text=modul_name.AppConfig.VERSION)
        self.version_lb.config(font=("Ubuntu", 11), foreground="#00c4ff")
        self.version_lb.grid(column=0, row=0, rowspan=4, padx=10, pady=10)

        Tooltip(
            self.version_lb,
            f"Version: {modul_name.AppConfig.VERSION[2:]}",
            self.tooltip_state,
        )
        self.load_button = ttk.Button(
            self.menu_frame,
            text=_("Load Log"),
            style="Toolbutton",
            command=lambda: self.directory_load(modul_name, _),
        )
        self.load_button.grid(column=1, row=0)
        self.options_btn = ttk.Menubutton(self.menu_frame, text=_("Options"))
        self.options_btn.grid(column=2, row=0)

        Tooltip(self.options_btn, modul_name.Msg.TTIP["settings"], self.tooltip_state)

        self.set_update = tk.IntVar()
        self.settings = tk.Menu(self, relief="flat")
        self.options_btn.configure(menu=self.settings, style="Toolbutton")
        self.settings.add_checkbutton(
            label=_("Disable Updates"),
            command=lambda: self.update_setting(self.set_update.get(), modul_name, _),
            variable=self.set_update,
        )

        self.updates_lb = ttk.Label(self.menu_frame, textvariable=self.update_label)
        self.updates_lb.grid(column=5, row=0, padx=10)
        self.updates_lb.grid_remove()
        self.update_label.trace_add("write", self.update_label_display)
        self.update_foreground.trace_add("write", self.update_label_display)
        res = GiteaUpdate.api_down(
            modul_name.AppConfig.UPDATE_URL,
            modul_name.AppConfig.VERSION,
            ConfigManager.get("updates"),
        )
        self.update_ui_for_update(res, modul_name, _)

        # Tooltip Menu
        self.settings.add_command(
            label=self.tooltip_label.get(),
            command=lambda: self.tooltips_toggle(modul_name, _),
        )
        # Label show dark or light
        self.theme_label = tk.StringVar()
        self.update_theme_label(modul_name, _)
        self.settings.add_command(
            label=self.theme_label.get(),
            command=lambda: self.on_theme_toggle(modul_name, _),
        )

        # About BTN Menu / Label
        self.about_btn = ttk.Button(
            self.menu_frame,
            text=_("About"),
            style="Toolbutton",
            command=lambda: self.about(modul_name, _),
        )
        self.about_btn.grid(column=3, row=0)
        self.readme = tk.Menu(self)
        # self.grid_rowconfigure(0, weight=)
        self.grid_rowconfigure(1, weight=25)
        self.grid_columnconfigure(0, weight=1)

    # Method that is called when the variable changes
    def update_label_display(self, *args):
        # Set the foreground color
        self.updates_lb.configure(foreground=self.update_foreground.get())

        # Show or hide the label based on whether it contains text
        if self.update_label.get():
            # Make sure the label is in the correct position every time it's shown
            self.updates_lb.grid(column=5, row=0, padx=10)
        else:
            self.updates_lb.grid_remove()

    # Update the labels based on the result
    def update_ui_for_update(self, res, modul_name, _):
        """Update UI elements based on an update check result"""
        # First, remove the update button if it exists to avoid conflicts
        if hasattr(self, "update_btn"):
            self.update_btn.grid_forget()
            delattr(self, "update_btn")

        if res == "False":
            self.set_update.set(value=1)
            self.update_label.set(_("Update search off"))
            self.update_tooltip.set(_("Updates you have disabled"))
            # Clear the foreground color as requested
            self.update_foreground.set("")
            # Set the tooltip for the label
            Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state)

        elif res == "No Internet Connection!":
            self.update_label.set(_("No Server Connection!"))
            self.update_foreground.set("red")
            # Set the tooltip for "No Server Connection"
            Tooltip(
                self.updates_lb,
                _("Could not connect to update server"),
                self.tooltip_state,
            )

        elif res == "No Updates":
            self.update_label.set(_("No Updates"))
            self.update_tooltip.set(_("Congratulations! Wire-Py is up to date"))
            self.update_foreground.set("")
            # Set the tooltip for the label
            Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state)

        else:
            self.set_update.set(value=0)
            update_text = f"Update {res} {_('available!')}"

            # Clear the label text since we'll show the button instead
            self.update_label.set("")

            # Create the update button
            self.update_btn = ttk.Menubutton(self.menu_frame, text=update_text)
            self.update_btn.grid(column=5, row=0, padx=0)
            Tooltip(
                self.update_btn, _("Click to download new version"), self.tooltip_state
            )

            self.download = tk.Menu(self, relief="flat")
            self.update_btn.configure(menu=self.download, style="Toolbutton")
            self.download.add_command(
                label=_("Download"),
                command=lambda: GiteaUpdate.download(
                    f"{modul_name.AppConfig.DOWNLOAD_URL}/{res}.zip", res
                ),
            )

    @staticmethod
    def about(modul_name, _) -> None:
        """
        a tk.Toplevel window
        """

        def link_btn() -> None:
            webbrowser.open("https://git.ilunix.de/punix/shared_libs")

        msg_t = _(
            "Logviewer a simple Gui for View Logfiles.\n\n"
            "Logviewer is open source software written in Python.\n\n"
            "Email: polunga40@unity-mail.de also likes for donation.\n\n"
            "Use without warranty!\n"
        )

        LxTools.msg_window(
            modul_name.AppConfig.IMAGE_PATHS["icon_log"],
            modul_name.AppConfig.IMAGE_PATHS["icon_log"],
            _("Info"),
            msg_t,
            _("Go to shared_libs git"),
            link_btn,
        )

    def update_setting(self, update_res, modul_name, _) -> None:
        """write off or on in file
        Args:
            update_res (int): argument that is passed contains 0 or 1
        """
        if update_res == 1:
            # Disable updates
            ConfigManager.set("updates", "off")
            # When updates are disabled, we know the result should be "False"
            self.update_ui_for_update("False", modul_name, _)
        else:
            # Enable updates
            ConfigManager.set("updates", "on")
            # When enabling updates, we need to actually check for updates
            try:
                # Force a fresh check by passing "on" as the update setting
                res = GiteaUpdate.api_down(
                    modul_name.AppConfig.UPDATE_URL, modul_name.AppConfig.VERSION, "on"
                )

                # Make sure the UI is updated regardless of the previous state
                if hasattr(self, "update_btn"):
                    self.update_btn.grid_forget()
                if hasattr(self, "updates_lb"):
                    self.updates_lb.grid_forget()

                # Now update the UI with the fresh result
                self.update_ui_for_update(res, modul_name, _)
            except Exception as e:
                logging.error(f"Error checking for updates: {e}")
                # Fallback to a default message if there's an error
                self.update_ui_for_update("No Internet Connection!", modul_name, _)

    def tooltip_update_label(self, modul_name, _) -> None:
        """Updates the tooltip menu label based on the current tooltip status"""
        # Set the menu text based on the current status
        if self.tooltip_state.get():
            # If tooltips are enabled, the menu option should be to disable them
            self.tooltip_label.set(_("Disable Tooltips"))
        else:
            # If tooltips are disabled, the menu option should be to enable them
            self.tooltip_label.set(_("Enable Tooltips"))

    def tooltips_toggle(self, modul_name, _):
        """
        Toggles the visibility of tooltips (on/off) and updates
        the corresponding menu label. Inverts the current tooltip state
        (`self.tooltip_state`), saves the new value in the configuration,
        and applies the change immediately. Updates the menu entry's label to
        reflect the new tooltip status (e.g., "Tooltips: On" or "Tooltips: Off").
        """
        # Toggle the boolean state
        new_bool_state = not self.tooltip_state.get()
        # Save the converted value in the configuration
        ConfigManager.set("tooltips", str(new_bool_state))
        # Update the tooltip_state variable for immediate effect
        self.tooltip_state.set(new_bool_state)

        # Update the menu label
        self.tooltip_update_label(modul_name, _)

        # Update the menu entry - find the correct index
        # This assumes it's the third item (index 2) in your menu
        self.settings.entryconfigure(1, label=self.tooltip_label.get())

    def update_theme_label(self, modul_name, _) -> None:
        """Update the theme label based on the current theme"""
        current_theme = ConfigManager.get("theme")
        if current_theme == "light":
            self.theme_label.set(_("Dark"))
        else:
            self.theme_label.set(_("Light"))

    def on_theme_toggle(self, modul_name, _) -> None:
        """Toggle between light and dark theme"""
        current_theme = ConfigManager.get("theme")
        new_theme = "dark" if current_theme == "light" else "light"
        ThemeManager.change_theme(self, new_theme, new_theme)
        self.update_theme_label(modul_name, _)  # Update the theme label
        # Update Menulfield
        self.settings.entryconfigure(2, label=self.theme_label.get())

    def createWidgets(self, _):

        text_frame = ttk.Frame(self)
        text_frame.grid(row=1, column=0, padx=5, pady=5, sticky=tk.NSEW)
        text_frame.rowconfigure(0, weight=3)
        text_frame.columnconfigure(0, weight=1)
        next_frame = ttk.Frame(self)
        next_frame.grid(row=2, column=0, sticky=tk.NSEW)
        next_frame.rowconfigure(2, weight=1)
        next_frame.columnconfigure(1, weight=1)
        # Create a Text widget for displaying the log file
        self.text_area = tk.Text(
            text_frame, wrap=tk.WORD, padx=5, pady=5, relief="flat"
        )
        self.text_area.grid(row=0, column=0, sticky=tk.NSEW)
        self.text_area.tag_configure(
            "found-tag", foreground="yellow", background="green"
        )
        # Create a vertical scrollbar for the Text widget
        v_scrollbar = ttk.Scrollbar(
            text_frame, orient="vertical", command=self.text_area.yview
        )
        v_scrollbar.grid(row=0, column=1, sticky=tk.NS)
        self.text_area.configure(yscrollcommand=v_scrollbar.set)

        self._entry = ttk.Entry(next_frame)
        self._entry.bind("<Return>", lambda e: self._onFind())
        self._entry.grid(row=0, column=1, padx=5, sticky=tk.EW)
        # Add a context menu to the Text widget
        self.context_menu = tk.Menu(self, tearoff=0)
        self.context_menu.add_command(label=_("Copy"), command=self.copy_text)
        self.context_menu.add_command(label=_("Paste"), command=self.paste_into_entry)
        self.text_area.bind("<Button-3>", self.show_context_menu)
        self._entry.bind("<Button-3>", self.show_context_menu)

        search_button = ttk.Button(next_frame, text="Search", command=self._onFind)
        search_button.grid(row=0, column=0, padx=5, pady=5, sticky=tk.EW)

        delete_button = ttk.Button(
            next_frame, text="Delete_Log", command=self.delete_file
        )
        delete_button.grid(row=0, column=2, padx=5, pady=5, sticky=tk.EW)

    def show_text_menu(self, event):
        try:
            self.configure.tk_popup(event.x_root, event.y_root)
        finally:
            self.context_menu.grab_release()

    def copy_text(self):

        try:
            selected_text = self.text_area.selection_get()
            self.clipboard_clear()
            self.clipboard_append(selected_text)
        except tk.TclError:
            # No Text selected
            pass

    def show_context_menu(self, event):
        try:
            self.context_menu.tk_popup(event.x_root, event.y_root)
        finally:
            self.context_menu.grab_release()

    def paste_into_entry(self):
        try:
            text = self.clipboard_get()
            self._entry.delete(0, tk.END)
            self._entry.insert(tk.END, text)
        except tk.TclError:
            # No Text on Clipboard
            pass

    def _onFind(self):
        searchText = self._entry.get()
        if len(searchText) == 0:
            return

        # Set the search start position to the last found position (initial value: "1.0")
        start_pos = self.last_search_pos if hasattr(self, "last_search_pos") else "1.0"

        var = tk.IntVar()
        foundIndex = self.text_area.search(
            searchText,
            start_pos,
            stopindex=tk.END,
            nocase=tk.YES,
            count=var,
            regexp=tk.YES,
        )

        if not foundIndex:
            # No further entry found, reset to the beginning
            self.last_search_pos = "1.0"
            return

        count = var.get()
        lastIndex = self.text_area.index(f"{foundIndex} + {count}c")

        # Remove and reapply highlighting
        self.text_area.tag_remove("found-tag", "1.0", tk.END)
        self.text_area.tag_add("found-tag", foundIndex, lastIndex)

        # Update the start position for the next search
        self.last_search_pos = lastIndex
        self.text_area.see(foundIndex)

    def delete_file(self, modul_name):
        Path.unlink(modul_name.AppConfig.LOG_FILE_PATH)
        modul_name.AppConfig.ensure_log()

    def load_file(self, _, modul_name):

        try:
            if not modul_name.AppConfig.LOG_FILE_PATH:
                return

            with open(
                modul_name.AppConfig.LOG_FILE_PATH, "r", encoding="utf-8"
            ) as file:
                self.text_area.delete(1.0, tk.END)
                self.text_area.insert(tk.END, file.read())
        except Exception as e:
            logging.error(_(f"A mistake occurred: {str(e)}"))
            LxTools.msg_window(
                modul_name.AppConfig.IMAGE_PATHS["icon_error"],
                modul_name.AppConfig.IMAGE_PATHS["icon_log"],
                "LogViewer",
                _(f"A mistake occurred:\n{str(e)}\n"),
            )

    def directory_load(self, modul_name, _):

        filepath = filedialog.askopenfilename(
            initialdir=f"{Path.home() / ".local/share/lxlogs/"}",
            title="Select a Logfile File",
            filetypes=[("Logfiles", "*.log")],
        )

        try:
            with open(filepath, "r", encoding="utf-8") as file:
                self.text_area.delete(1.0, tk.END)
                self.text_area.insert(tk.END, file.read())
        except (IsADirectoryError, TypeError, FileNotFoundError):
            print("File load: abort by user...")
        except Exception as e:
            logging.error(_(f"A mistake occurred: {e}"))
            LxTools.msg_window(
                modul_name.AppConfig.IMAGE_PATHS["icon_error"],
                modul_name.AppConfig.IMAGE_PATHS["icon_log"],
                "LogViewer",
                _(f"A mistake occurred:\n{e}\n"),
            )


def main():

    # Create an ArgumentParser object
    parser = argparse.ArgumentParser(
        description="LogViewer with optional module loading."
    )
    parser.add_argument(
        "--modul",
        type=str,
        default="logview_app_config",
        help="Give the name of the module to load.",
    )
    args = parser.parse_args()
    import importlib

    try:
        modul = importlib.import_module(args.modul)
    except ModuleNotFoundError:
        print(f"Modul '{args.modul}' not found")
        print("For help use logviewer -h")
        sys.exit(1)
    except Exception as e:
        print(f"Error load Modul: {str(e)}")
        sys.exit(1)

    prepare_app_environment()
    app = LogViewer(modul)
    LogConfig.logger(ConfigManager.get("logfile"))
    """
    the hidden files are hidden in Filedialog
    """
    try:
        app.tk.call("tk_getOpenFile", "-foobarbaz")
    except TclError:
        pass
    app.tk.call("set", "::tk::dialog::file::showHiddenBtn", "1")
    app.tk.call("set", "::tk::dialog::file::showHiddenVar", "0")
    app.mainloop()


if __name__ == "__main__":
    main()