from gi.repository import Adw, Gio, GLib, GObject, Gtk

import logging

import iotas.config_manager
from iotas.category_manager import CategoryManager
from iotas import const
from iotas.editor import Editor
from iotas.index import Index
from iotas.note import Note
from iotas.note_database import NoteDatabase
from iotas.note_manager import NoteManager
from iotas.nextcloud_login_window import NextcloudLoginWindow
from iotas.sync_manager import SyncManager


@Gtk.Template(resource_path="/org/gnome/gitlab/cheywood/Iotas/ui/window.ui")
class Window(Adw.ApplicationWindow):
    __gtype_name__ = "Window"

    _navigation = Gtk.Template.Child()
    _index = Gtk.Template.Child()
    _editor = Gtk.Template.Child()
    _editor_page = Gtk.Template.Child()

    def __init__(self, app: Adw.Application, db: NoteDatabase, sync_manager: SyncManager):
        super().__init__(application=app)

        self.set_icon_name(const.APP_ID)

        if iotas.const.IS_DEVEL:
            self.add_css_class("devel")

        self.app = self.get_application()

        self.__db = db
        self.__sync_manager = sync_manager

        self.__category_manager = CategoryManager(self.__db)
        self.__note_manager = NoteManager(self.__db, self.__category_manager)

        self.__initialised = False
        self.__using_keyboard_navigation = False
        self.__secret_service_failed = False
        self.__open_note_after_init = None
        self.__create_note_after_init = False

        self.__setup_actions()

        self._index.setup(self.__note_manager, self.__category_manager, self.__sync_manager)
        self._index.connect("note-opened", self.__on_note_opened)

        self._editor.setup(self.__category_manager.tree_store)
        self._editor.connect("note-modified", self.__on_note_edited)
        self._editor.connect("note-deleted", self.__on_editor_note_deleted)
        self._editor.connect("category-changed", self.__on_note_category_changed)
        self._editor.connect("exit", self.__on_editor_exit)

        self.__note_manager.connect("sync-requested", self.__on_sync_requested)

        self.__sync_manager.connect("ready", self.__on_sync_ready)
        self.__sync_manager.connect("note-conflict", self.__on_note_conflict)
        self.__sync_manager.connect("secret-service-failure", self.__on_secret_service_failure)
        self.__sync_manager.connect("notify::authenticated", self.__on_sync_authenticated_changed)
        self.__sync_manager.connect(
            "remote-category-sanitised", self.__on_remote_category_sanitised
        )

        self.__note_manager.connect(
            "initial-load-complete", self.__on_initial_load_from_db_complete
        )

        self.connect("close-request", self.__on_close_request)

        (width, height) = iotas.config_manager.get_window_size()
        self.set_default_size(width, height)
        self._index.update_layout_for_initial_width(width)
        self._index.active = True
        self._editor.active = False

    @GObject.Property(type=bool, default=False)
    def using_keyboard_navigation(self) -> bool:
        return self.__using_keyboard_navigation

    @using_keyboard_navigation.setter
    def using_keyboard_navigation(self, value: bool) -> None:
        self.__using_keyboard_navigation = value

    @Gtk.Template.Callback()
    def _on_page_pushed(self, _obj: GObject.Object) -> None:
        self._index.active = False
        self._editor.active = True

    @Gtk.Template.Callback()
    def _on_page_popped(self, _obj: GObject.Object, _page: Adw.NavigationPage) -> None:
        note = self._editor.current_note
        self.__note_manager.persist_note_after_closing(note)
        self._index.refresh_after_note_closed(note)
        self._editor.close_note()
        self._index.active = True
        self._editor.active = False

    def __setup_actions(self) -> None:
        action_group = Gio.SimpleActionGroup.new()
        app = Gio.Application.get_default()

        action = Gio.SimpleAction.new("fullscreen")
        action.connect("activate", self.__toggle_fullscreen)
        action_group.add_action(action)
        app.set_accels_for_action("win.fullscreen", ["F11"])

        action = Gio.SimpleAction.new("refresh")
        action.connect("activate", self.__on_refresh)
        action_group.add_action(action)
        app.set_accels_for_action("win.refresh", ["<Control>r"])
        action.set_enabled(False)

        action = Gio.SimpleAction.new("start-nextcloud-signin")
        action.connect("activate", self.__on_start_nextcloud_signin)
        action_group.add_action(action)

        action = Gio.SimpleAction.new("create-note-from-cli")
        action.connect("activate", self.__on_create_note)
        action_group.add_action(action)

        action = Gio.SimpleAction.new("open-note", GLib.VariantType("u"))
        action.connect("activate", self.__on_open_note)
        action_group.add_action(action)

        action = Gio.SimpleAction.new("search-from-cli", GLib.VariantType("s"))
        action.connect("activate", self.__on_search_from_cli)
        action_group.add_action(action)

        self.insert_action_group("win", action_group)
        self.__action_group = action_group

    def __on_note_opened(self, _obj: Index, note: Note, immediate: bool) -> None:
        self.__open_note(note, immediate)

    def __on_editor_note_deleted(self, _obj: GObject.Object, note: Note) -> None:
        if not self._navigation.get_visible_page() is self._editor_page:
            return

        self._navigation.pop()
        self.__sync_manager.flush_pending_deletions()
        self.__note_manager.delete_notes([note])
        self._index.update_for_note_deletions([note])

    def __on_editor_exit(self, _obj: GObject.Object) -> None:
        self._navigation.pop()

    def __on_note_conflict(self, _obj: GObject.Object, note: Note) -> None:
        if self._editor.current_note is not None and note is self._editor.current_note:
            logging.info("Note being currently edited in conflict")
            self._editor.current_note.handling_conflict = True
            self._navigation.pop()
            self._index.show_note_conflict_alert()

    def __on_sync_ready(self, _obj: GObject.Object, new_setup: bool) -> None:
        action = self.__action_group.lookup("refresh")
        action.set_enabled(True)
        action = self.__action_group.lookup("start-nextcloud-signin")
        action.set_enabled(False)

    def __on_refresh(
        self,
        _obj: GObject.Object,
        _value: GObject.ParamSpec,
    ) -> None:
        """Refresh from server."""
        self.__sync_manager.sync_now()

    def __on_start_nextcloud_signin(
        self,
        _obj: GObject.Object,
        _value: GObject.ParamSpec,
    ) -> None:
        """Perform Nextcloud sign in."""
        window = NextcloudLoginWindow(self.__sync_manager, self.__secret_service_failed)
        window.set_transient_for(self)
        window.present()

    def __on_sync_authenticated_changed(
        self,
        _obj: GObject.Object,
        _value: GObject.ParamSpec,
    ) -> None:
        self._editor.set_sync_authenticated(self.__sync_manager.authenticated)

    def __toggle_fullscreen(self, _action: Gio.SimpleAction, _param: GLib.Variant) -> None:
        hiding_headerbar = iotas.config_manager.get_hide_editor_headerbar_when_fullscreen()
        if self.is_fullscreen():
            self.unfullscreen()
            if hiding_headerbar and not self._editor.headerbar_manually_hidden:
                self._editor.show_headerbar()
        else:
            self.fullscreen()
            if hiding_headerbar:
                editor_active = self._navigation.get_visible_page() is self._editor_page
                self._editor.hide_headerbar(editor_active)

    def __on_close_request(self, _obj: GObject.Object) -> None:
        """On window destroyed."""
        self.__sync_manager.close()
        if not self.is_fullscreen():
            iotas.config_manager.set_window_size(self.get_width(), self.get_height())
        iotas.config_manager.set_first_start(False)
        self.close()

    def __on_secret_service_failure(self, _obj: GObject.Object) -> None:
        self.__secret_service_failed = True
        self._index.show_secret_service_failure_alert()

    def __on_note_edited(self, _editor: Editor, note: Note) -> None:
        self.__note_manager.persist_note_while_editing(note)

    def __on_note_category_changed(
        self, _obj: GObject.Object, note: Note, old_category: str
    ) -> None:
        self.__note_manager.persist_note_category(note, old_category)

    def __on_sync_requested(self, _obj: GObject.Object) -> None:
        self.__sync_manager.sync_now()

    def __on_remote_category_sanitised(
        self, _obj: SyncManager, old_category: str, new_category: str
    ) -> None:
        self.__category_manager.note_category_changed(old_category, new_category)

    def __on_create_note(
        self,
        _obj: GObject.Object,
        _value: GObject.ParamSpec,
    ) -> None:
        """Create a new note and edit it."""
        if self.__initialised:
            if self._editor.active:
                self._navigation.pop()
            self.activate_action("index.create-note")
        else:
            self.__create_note_after_init = True

    def __on_open_note(
        self,
        _obj: GObject.Object,
        note_id: GObject.ParamSpec,
    ) -> None:
        note_id = note_id.get_uint32()
        if self.__initialised:
            if self._editor.active:
                self._navigation.pop()
            self.__open_note_by_id(note_id, False)
        else:
            self.__open_note_after_init = note_id

    def __on_search_from_cli(
        self,
        _obj: GObject.Object,
        param: GObject.ParamSpec,
    ) -> None:
        search_term = param.get_string()
        if self._editor.active:
            self._navigation.pop()
        self._index.search_from_cli(search_term)

    def __on_initial_load_from_db_complete(
        self, _obj: GObject.Object, older_notes_loaded: bool
    ) -> None:
        self.__initialised = True
        if self.__create_note_after_init:
            note = self.__note_manager.create_note(self.__category_manager.all_category)
            self.__open_note(note, True)
        elif self.__open_note_after_init is not None:
            self.__open_note_by_id(self.__open_note_after_init, True)

    def __open_note(self, note: Note, immediate: bool) -> None:
        if immediate:
            self._navigation.set_animate_transitions(False)
        if not note.content_loaded:
            self.__db.populate_note_content(note)
        # Handle opening a note from CLI / shell search provider when another note is open
        if self._editor.active:
            self._navigation.pop()
        self._editor.init_note(note)
        self._navigation.push(self._editor_page)
        if immediate:
            self._navigation.set_animate_transitions(True)

    def __open_note_by_id(self, note_id: int, immediate: bool) -> None:
        note = self.__note_manager.fetch_note_by_id(note_id)
        if note is None:
            logging.error(f"Failed to find note with id {note_id}")
            return
        self.__open_note(note, immediate)
