""" settings dialog

This file is part of Turtle.

Turtle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Turtle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Turtle. If not, see <https://www.gnu.org/licenses/>.
"""
import os
from subprocess import check_output
import gi
import pygit2
import turtlevcs
from turtlevcs.dialogs import Notification, Information
from turtlevcs.service import TurtleServiceConnector
from turtlevcs.dialogs.plugin_installer import PluginInstaller
from turtlevcs.dialogs.base import DialogBase

gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Adw
from gi.repository import Gtk, Gdk, Gio, GObject


@Gtk.Template(filename=f"{os.path.dirname(os.path.abspath(__file__))}/ui/settings_menu.ui")
class SettingsMenuWindow(Adw.MessageDialog):
    """ settings menu dialog """
    __gtype_name__ = "SettingsMenuWindow"

    menu_entries = Gtk.Template.Child()
    submenu_entries = Gtk.Template.Child()
    hidden_entries = Gtk.Template.Child()

    drag_x = 0
    drag_y = 0

    def __init__(self, parent=None):
        Adw.MessageDialog.__init__(self, transient_for=parent)

        self.settings = turtlevcs.get_settings()
        items = self.settings.get_strv("menu-items")
        for item in items:
            row = Gtk.ListBoxRow()
            row.set_selectable(False)
            if item.startswith("*"):
                self.menu_entries.append(row)
            elif item.startswith("+"):
                self.submenu_entries.append(row)
            else:
                self.hidden_entries.append(row)

            identifier = item[1:]
            text = ""
            for i, t, _, __ in turtlevcs.MENU_ITEMS:
                if identifier == i:
                    text = t
                    break
            label = Gtk.Label(label=text)
            row.identifier = identifier
            row.set_child(label)

            drop_controller = Gtk.DropControllerMotion()

            drag_source = Gtk.DragSource(
                actions=Gdk.DragAction.MOVE,
            )

            row.add_controller(drag_source)
            row.add_controller(drop_controller)

            drag_source.connect("prepare", self._on_prepare, row)
            drag_source.connect("drag-begin", self._on_drag_begin, row)

            drop_controller.connect(
                "enter",
                lambda _target, _x, _y, row: self.menu_entries.drag_highlight_row(row), row
            )

            drop_controller.connect(
                "leave",
                lambda _target: self.menu_entries.drag_unhighlight_row())

        def add_drop_target(menu):
            drop_target = Gtk.DropTarget.new(Gtk.ListBoxRow, Gdk.DragAction.MOVE)
            menu.add_controller(drop_target)
            drop_target.connect("drop", self._on_drop)

        add_drop_target(self.menu_entries)
        add_drop_target(self.submenu_entries)
        add_drop_target(self.hidden_entries)

        self.add_response("ok", "Ok")
        self.add_response("cancel", "Cancel")
        self.add_response("reset", "Reset")
        self.set_response_appearance("reset", Adw.ResponseAppearance.DESTRUCTIVE)

    def _on_prepare(self, _source, x, y, row):
        self.drag_x = x
        self.drag_y = y

        value = GObject.Value()
        value.init(Gtk.ListBoxRow)
        value.set_object(row)

        return Gdk.ContentProvider.new_for_value(value)

    def _on_drag_begin(self, _source, drag, row):
        allocation = row.get_allocation()
        drag_widget = Gtk.ListBox()

        drag_widget.set_size_request(allocation.width, allocation.height)
        drag_widget.add_css_class("boxed-list")

        text = row.get_child().get_label()
        label = Gtk.Label(label=text)
        drag_row = Gtk.ListBoxRow(child=label)

        drag_widget.append(drag_row)
        drag_widget.drag_highlight_row(drag_row)

        icon = Gtk.DragIcon.get_for_drag(drag)
        icon.set_child(drag_widget)

        drag.set_hotspot(self.drag_x, self.drag_y)

    def _on_drop(self, drop, value, _x, y):
        menu = drop.get_widget()
        target_row = menu.get_row_at_y(y)
        target_index = None
        if target_row:
            target_index = target_row.get_index()
        else:
            target_index = -1

        if not value:
            return False

        source_menu = value.get_parent()
        source_menu.remove(value)
        menu.insert(value, target_index)
        if target_row:
            target_row.set_state_flags(Gtk.StateFlags.NORMAL, True)

        return True


@Gtk.Template(filename=f"{os.path.dirname(os.path.abspath(__file__))}/ui/settings.ui")
class SettingsWindow(Adw.PreferencesWindow, DialogBase):
    """ settings dialog """
    __gtype_name__ = "SettingsWindow"

    git_group = Gtk.Template.Child()
    git_name = Gtk.Template.Child()
    git_mail = Gtk.Template.Child()
    git_selected_credentials = Gtk.Template.Child()
    git_credential_ssh_agent = Gtk.Template.Child()
    git_credential_ssh_key = Gtk.Template.Child()
    git_agent_available = Gtk.Template.Child()
    git_agent = Gtk.Template.Child()
    git_ssh_key = Gtk.Template.Child()
    git_gpg_sign = Gtk.Template.Child()
    git_gpg_key = Gtk.Template.Child()
    git_https_password_row = Gtk.Template.Child()
    git_https_store_password = Gtk.Template.Child()
    plugins_enable_everywhere = Gtk.Template.Child()
    plugins_restrict_to_home = Gtk.Template.Child()
    plugins_show_emblems = Gtk.Template.Child()
    plugins_show_turtle_emblem = Gtk.Template.Child()
    plugins_show_status_emblem_row = Gtk.Template.Child()
    plugins_show_status_emblem = Gtk.Template.Child()
    plugins_show_branch_in_menu = Gtk.Template.Child()
    plugins_show_compare = Gtk.Template.Child()
    plugins_install_row = Gtk.Template.Child()
    dialogs_commit_selection = Gtk.Template.Child()
    dialogs_commit_unversioned = Gtk.Template.Child()
    dialogs_commit_ignored = Gtk.Template.Child()
    dialogs_stage_information = Gtk.Template.Child()
    dialogs_log_upstream_branch = Gtk.Template.Child()
    dialogs_log_all_branches = Gtk.Template.Child()
    dialogs_log_save_name_filter = Gtk.Template.Child()
    git_store_password = Gtk.Template.Child()
    credentials_group = Gtk.Template.Child()
    debugging_full_trace = Gtk.Template.Child()
    service_page = Gtk.Template.Child()
    service_pid = Gtk.Template.Child()
    service_shutdown_row = Gtk.Template.Child()

    open_dialog = None

    repo = None
    settings = None

    current_service_pid = None

    def __init__(self, path=None):
        Adw.PreferencesWindow.__init__(self)
        DialogBase.__init__(self, path, no_turtle=True)

        self.settings = turtlevcs.get_settings()

        try:
            if path is not None:
                self.repo = pygit2.Repository(path)
        except Exception:
            pass

        try:
            if self.repo:
                author = turtlevcs.get_author(self.repo)
                self.git_name.set_text(author.name)
                self.git_mail.set_text(author.email)
            else:
                text = "only available inside repo"
                self.git_name.set_text(text)
                self.git_mail.set_text(text)
        except Exception as ex:
            window = Notification(str(ex), title="Could not get author information", parent=self)
            window.set_visible(True)

        if "SSH_AUTH_SOCK" in os.environ:
            self.git_agent_available.set_visible(False)

        if turtlevcs.is_flatpak():
            self.service_shutdown_row.set_visible(False)
        else:
            self.plugins_install_row.set_visible(False)

        if not turtlevcs.can_store_password():
            self.git_https_password_row.set_visible(False)
            self.credentials_group.set_visible(False)

        self._init_settings()

        self._refresh_service_status()

    def _init_settings(self):
        if self.settings:
            # git
            self.settings.bind(
                "ssh-key",
                self.git_ssh_key,
                "text",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "agent",
                self.git_agent,
                "text",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "specific-ssh-key",
                self.git_credential_ssh_key,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "enable-store-passwords",
                self.git_https_store_password,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "gpg-auto-sign",
                self.git_gpg_sign,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "gpg-key",
                self.git_gpg_key,
                "text",
                Gio.SettingsBindFlags.DEFAULT)

            self.git_credential_ssh_key.set_active(self.settings.get_boolean("specific-ssh-key"))
            self._credential_type_changed_handler(self.git_credential_ssh_key)

            # plugins
            self.settings.bind(
                "enable-everywhere",
                self.plugins_enable_everywhere,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "restrict-to-home",
                self.plugins_restrict_to_home,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "show-emblems",
                self.plugins_show_emblems,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "show-turtle-emblem",
                self.plugins_show_turtle_emblem,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "show-status-emblem",
                self.plugins_show_status_emblem,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "show-branch-in-menu",
                self.plugins_show_branch_in_menu,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "show-compare",
                self.plugins_show_compare,
                "active",
                Gio.SettingsBindFlags.DEFAULT)

            # defaults
            # commit
            self.settings.bind(
                "commit-show-unversioned",
                self.dialogs_commit_unversioned,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "commit-show-ignored",
                self.dialogs_commit_ignored,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "credentials-store-password",
                self.git_store_password,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            # cannot bind enum to AdwComboRow, sync it manually
            default_commit_selection = self.settings.get_enum("default-commit-selection")
            self.dialogs_commit_selection.set_selected(default_commit_selection)
            self.dialogs_commit_selection.connect(
                "notify::selected",
                self._on_dialogs_commit_selection_changed)
            # stage
            self.settings.bind(
                "stage-show-information",
                self.dialogs_stage_information,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            # log
            self.settings.bind(
                "log-show-upstream",
                self.dialogs_log_upstream_branch,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "log-show-all-branches",
                self.dialogs_log_all_branches,
                "active",
                Gio.SettingsBindFlags.DEFAULT)
            self.settings.bind(
                "log-save-name-filter",
                self.dialogs_log_save_name_filter,
                "active",
                Gio.SettingsBindFlags.DEFAULT)

            # debugging
            self.settings.bind(
                "full-exception-trace",
                self.debugging_full_trace,
                "active",
                Gio.SettingsBindFlags.DEFAULT)

    def _on_dialogs_commit_selection_changed(self, _widget, _property):
        default_commit_selection = self.dialogs_commit_selection.get_selected()
        self.settings.set_enum("default-commit-selection", default_commit_selection)


    @Gtk.Template.Callback()
    def _show_emblems_help_clicked(self, _widget):
        grid = Gtk.Grid()
        grid.set_row_spacing(6)
        grid.set_column_spacing(24)

        def add_grid_row(status, row):
            icon = Gtk.Image.new_from_icon_name(turtlevcs.status_icon_map[status])
            grid.attach(icon, 0, row, 1, 1)

            label = Gtk.Label()
            label.set_label(turtlevcs.status_friendly_name_map[status])
            label.set_hexpand(True)
            label.set_halign(Gtk.Align.START)
            grid.attach(label, 1, row, 1, 1)

        # CURRENT is not part of the FileStatus iteration
        add_grid_row(pygit2.enums.FileStatus.CURRENT, 0)
        row = 1
        for status in pygit2.enums.FileStatus:
            add_grid_row(status, row)
            row += 1

        information = Information(title="Emblems", content=grid, parent=self)
        information.set_visible(True)

    @Gtk.Template.Callback()
    def _modify_menu_items(self, _widget):
        settings = SettingsMenuWindow(self)
        settings.connect("response", self._modify_menu_items_finished)
        settings.present()

    def _modify_menu_items_finished(self, widget, response):
        if response == "ok":
            def create_menu_list(menu, prefix):
                menu_list = []
                i = 0
                entry = menu.get_row_at_index(i)
                while entry:
                    i += 1
                    menu_list.append(prefix + entry.identifier)
                    entry = menu.get_row_at_index(i)

                return menu_list

            full_menu_list = []
            full_menu_list += create_menu_list(widget.menu_entries, "*")
            full_menu_list += create_menu_list(widget.submenu_entries, "+")
            full_menu_list += create_menu_list(widget.hidden_entries, "-")

            self.settings.set_strv("menu-items", full_menu_list)
        elif response == "reset":
            self.settings.reset("menu-items")


    @Gtk.Template.Callback()
    def _install_files_plugin(self, _widget):
        installer = PluginInstaller(self)
        installer.present()

    @Gtk.Template.Callback()
    def _credential_type_changed_handler(self, _widget):
        is_agent = self.git_credential_ssh_agent.get_active()
        self.git_agent.set_sensitive(is_agent)
        self.git_ssh_key.set_sensitive(not is_agent)

    @Gtk.Template.Callback()
    def _search_ssh_key(self, _widget):
        if not self.open_dialog:
            self.open_dialog = Gtk.FileChooserNative.new(
                title="Choose an SSH private key",
                parent=self, action=Gtk.FileChooserAction.OPEN)
            self.open_dialog.connect("response", self._search_ssh_key_callback)
        path = self.git_ssh_key.get_text()
        if len(path) > 0 and os.path.exists(path):
            self.open_dialog.set_file(Gio.File.new_for_path(path))
        self.open_dialog.show()

    def _search_ssh_key_callback(self, dialog, response):
        if response == Gtk.ResponseType.ACCEPT:
            path = dialog.get_file().get_path()
            if os.path.exists(path):
                self.git_ssh_key.set_text(path)

    @Gtk.Template.Callback()
    def _refresh_service_status(self, _widget=None):
        pids = []
        container = os.getenv("container")
        if not container:
            output = check_output(["ps", "x"]).decode("utf8")
            processes = output.split("\n")

            for process in processes:
                # check if the service runs in flatpak container
                if "/app/bin/turtle_service" in process:
                    values = process.strip().split(" ", 1)
                    pid = values[0]
                    pids.append(pid)

            if len(pids) > 0:
                container = "flatpak, real pid: " + ",".join(pids)

        try:
            turtle_service = TurtleServiceConnector()
            self.current_service_pid = turtle_service.get_pid()

            self.service_pid.set_label(
                str(self.current_service_pid) + (" (" + container + ")" if container else ""))
        except Exception as _ex:
            self.service_pid.set_label("(unavailable)")

    @Gtk.Template.Callback()
    def _restart_service(self, _widget=None):
        # ensure correct service pid
        self._refresh_service_status()

        if self.current_service_pid and not turtlevcs.is_flatpak():
            _ = check_output(["kill", str(self.current_service_pid)]).decode("utf8")

        # restart service
        self._refresh_service_status()
