from gettext import gettext as _
from gi.repository import Adw, Gdk, GLib, Gtk

from threading import Thread
from typing import Optional

import iotas.config_manager
from iotas.nextcloud_sync_worker import LoginRequestResult
from iotas.sync_manager import SyncManager


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

    LOGIN_ENDPOINT = "/index.php/login/v2"
    CHECK_LOGIN_INTERVAL = 1000

    _stack = Gtk.Template.Child()
    _intro = Gtk.Template.Child()
    _secret_service_failure = Gtk.Template.Child()
    _entry = Gtk.Template.Child()
    _loading = Gtk.Template.Child()
    _finished = Gtk.Template.Child()
    _continue_button = Gtk.Template.Child()
    _cancel_button = Gtk.Template.Child()
    _url_entry = Gtk.Template.Child()
    _spinner = Gtk.Template.Child()
    _waiting_label = Gtk.Template.Child()
    _syncing_label = Gtk.Template.Child()
    _entry_notification = Gtk.Template.Child()
    _instance_label = Gtk.Template.Child()

    _self_signed_certificate_handling = Gtk.Template.Child()

    def __init__(self, sync_manager: SyncManager, secret_service_failed: bool):
        super().__init__()
        self._continue_button.grab_focus()
        self.__sync_manager = sync_manager
        self.__timeout_id = None
        if secret_service_failed:
            self._stack.set_visible_child(self._secret_service_failure)
            self._continue_button.set_sensitive(False)

        self.__sync_manager.connect("finished", self.__on_initial_sync_finished)

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

    @Gtk.Template.Callback()
    def _on_continue_clicked(self, _button: Gtk.Button) -> None:
        child = self._stack.get_visible_child()
        if child == self._intro:
            self._stack.set_visible_child(self._entry)
            self._continue_button.set_sensitive(False)
            endpoint = iotas.config_manager.get_nextcloud_endpoint()
            if endpoint != "":
                self._url_entry.set_text(endpoint)
            self._url_entry.grab_focus()
            self._url_entry.set_position(-1)
        elif child == self._entry:
            self.__attempt_login()
        elif child == self._finished:
            self.close()

    @Gtk.Template.Callback()
    def _on_url_entry_changed(self, _entry: Gtk.Editable) -> None:
        self._continue_button.set_sensitive(self.__url_entry_valid())

    @Gtk.Template.Callback()
    def _on_url_entry_activated(self, _entry: Gtk.Entry) -> None:
        if self.__url_entry_valid():
            self.__attempt_login()

    @Gtk.Template.Callback()
    def _open_faq(self, _button: Gtk.Button) -> None:
        Gtk.show_uri(
            self,
            "https://gitlab.gnome.org/World/iotas/-/blob/main/README.md#faq-tips",
            Gdk.CURRENT_TIME,
        )

    @Gtk.Template.Callback()
    def _on_cancel_clicked(self, _button: Gtk.Button) -> None:
        self.close()

    def __on_close(self, _window: Gtk.Window) -> None:
        if self.__timeout_id is not None:
            GLib.source_remove(self.__timeout_id)
            self.__timeout_id = None

    def __on_login_success(self, storage_success: bool) -> None:
        if storage_success:
            if self._stack.get_visible_child() == self._loading:
                self._waiting_label.set_visible(False)
                self._syncing_label.set_visible(True)
        else:
            self._stack.set_visible_child(self._secret_service_failure)
            self._spinner.stop()

    def __on_initial_sync_finished(self, _obj: SyncManager, _changes: bool) -> None:
        self._spinner.stop()
        self._stack.set_visible_child(self._finished)
        self._cancel_button.set_sensitive(False)
        # Translators: Button
        self._continue_button.set_label(_("Finish"))
        self._continue_button.set_sensitive(True)
        self._continue_button.grab_focus()

        def removeprefix(instr, prefix):
            return instr[len(prefix) :] if instr.startswith(prefix) else instr

        clean_url = removeprefix(self.__server_url, "https://")
        clean_url = removeprefix(clean_url, "http://")

        self._instance_label.set_text(clean_url)

    def __url_entry_valid(self) -> bool:
        valid = False
        entry_text = self.__get_sanitised_url_entry()
        try:
            valid = GLib.uri_is_valid(entry_text, GLib.UriFlags.NONE)
        except GLib.GError:
            pass
        # GLib's Uri.is_valid appears to allow eg. http:someurl.com
        if valid:
            scheme = GLib.uri_peek_scheme(entry_text)
            if scheme not in ("http", "https") or not entry_text.startswith(f"{scheme}://"):
                valid = False
        return valid

    def __wait_and_check_success(self) -> None:
        self.__timeout_id = GLib.timeout_add(self.CHECK_LOGIN_INTERVAL, self.__check_success)

    def __check_success(self) -> None:
        self.__timeout_id = None

        def thread_do() -> None:
            (
                login_success,
                storage_success,
            ) = self.__sync_manager.check_for_login_success(self.__endpoint, self.__token)
            if login_success:
                GLib.idle_add(self.__on_login_success, storage_success)
            else:
                GLib.idle_add(self.__wait_and_check_success)

        thread = Thread(target=thread_do)
        thread.daemon = True
        thread.start()

    def __attempt_login(self) -> None:
        self._waiting_label.set_visible(True)
        self._syncing_label.set_visible(False)
        self._stack.set_visible_child(self._loading)
        self._spinner.start()
        self._continue_button.set_sensitive(False)

        self.__server_url = self.__get_sanitised_url_entry()
        iotas.config_manager.set_nextcloud_endpoint(self.__server_url)

        def thread_do() -> None:
            (result, endpoint, token) = self.__sync_manager.start_login(self.__server_url, self)
            GLib.idle_add(self.__handle_login_initialisation, result, endpoint, token)

        thread = Thread(target=thread_do)
        thread.daemon = True
        thread.start()

    def __handle_login_initialisation(
        self, result: LoginRequestResult, endpoint: Optional[str], token: Optional[str]
    ) -> None:
        if result == LoginRequestResult.SUCCESS:
            self.__endpoint = endpoint
            self.__token = token
            self.__wait_and_check_success()
        elif result == LoginRequestResult.FAILURE_SELF_SIGNED_SSL:
            self._spinner.stop()
            self._stack.set_visible_child(self._self_signed_certificate_handling)
        else:
            self._spinner.stop()
            self._stack.set_visible_child(self._entry)
            # Set the failed URL back into try text entry. This is to make any URI scheme
            # prefixing that has been done clear to the user.
            self._url_entry.set_text(self.__server_url)
            self._url_entry.set_position(-1)
            self._url_entry.grab_focus()

            # Translators: Description, notification
            self._entry_notification.show(_("Failed to start login. Wrong address?"), 4)

    def __get_sanitised_url_entry(self) -> str:
        entry_text = self._url_entry.get_text().strip().strip("/")
        scheme = GLib.uri_peek_scheme(entry_text)
        if scheme is None:
            entry_text = f"https://{entry_text}"
        return entry_text
