# Copyright (c) 2020-2022 Jan-Michael Brummer <jan.brummer@tabos.org>
#
# This program 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.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

from gettext import gettext as _
from gi.repository import Adw, Gtk, GLib
from saldo.fingerprint_verifier import FingerprintVerifier
import saldo.config_manager
import logging


@Gtk.Template(resource_path="/org/tabos/saldo/ui/locked_page.ui")
class LockedPage(Adw.Bin):
    __gtype_name__ = "LockedPage"

    _password_entry = Gtk.Template.Child()
    _unlock_button = Gtk.Template.Child()
    fingerprint_img = Gtk.Template.Child()
    banner = Gtk.Template.Child()

    FPRINT_MAX_TRIES = 3

    def __init__(self, window):
        super().__init__()

        self._window = window

        self.fprint: FingerprintVerifier | None = None
        self.fprint_tries = 0
        if saldo.config_manager.get_fingerprint_quickunlock():
            self._map_fingerprint_reader()
        self.quickunlock_failed = False

        self.event_controller = Gtk.EventControllerFocus.new()
        self.add_controller(self.event_controller)
        self.event_controller.connect("enter", self.on_enter)
        self.event_controller.connect("leave", self.on_leave)

        settings = self._window.get_application().settings
        settings.connect(
            "changed::fingerprint-quickunlock",
            self._on_fingerprint_settings_changed,
        )

    def on_enter(self, _user_data):
        if self._window.backend and self._window.backend.safe_password is not None:
            if saldo.config_manager.get_quickunlock() and not self.quickunlock_failed:
                self._window.show_banner("QuickUnlock active")

            # only start reading if there are still tries left
            if self.fprint_tries < self.FPRINT_MAX_TRIES:
                self._start_fingerprint_reader()
        else:
            logging.debug("Quickunlock disabled as no password is available.")

    def on_leave(self, _user_data):
        self._window.close_banner()
        if self.fprint_tries < self.FPRINT_MAX_TRIES:
            self._stop_fingerprint_reader()

    def do_realize(self):  # pylint: disable=arguments-differ
        Gtk.Widget.do_realize(self)
        self._password_entry.grab_focus()

    @Gtk.Template.Callback()
    def _on_unlock_button_clicked(self, button):
        entered_pwd = self._password_entry.get_text()
        quickunlock = (
            saldo.config_manager.get_quickunlock()
            and not self.quickunlock_failed
            and self._window.backend.safe_password
            and entered_pwd == self._window.backend.safe_password[-4:]
        )

        full_unlock = self._window.backend.is_safe_password_valid(
            self._password_entry.get_text()
        )

        if not quickunlock and not full_unlock:
            self._window._send_notification(_("Failed to Unlock Safe"))
            self._password_entry.add_css_class("error")
            self._stop_progress()

            return

        GLib.idle_add(self._load_database)

    def _start_progress(self):
        # Throws error: https://gitlab.gnome.org/GNOME/gtk/-/issues/5845
        self._password_entry.set_sensitive(False)
        self._unlock_button.set_sensitive(False)

    def _stop_progress(self):
        self._password_entry.set_sensitive(True)
        self._unlock_button.set_sensitive(True)

    def _load_database(self):
        self._start_progress()
        self._window.backend.load_database()
        self._stop_progress()
        self._password_entry.set_text("")
        self._password_entry.remove_css_class("error")
        self._reset_fingerprint_image()
        self._window.view = self._window.View.UNLOCKED

    @Gtk.Template.Callback()
    def _on_password_entry_activate(self, _):
        self._unlock_button.activate()

    def show_banner(self, label: str) -> None:
        self.banner.set_title(label)
        self.banner.set_revealed(True)

    def close_banner(self):
        self.banner.set_revealed(False)

    #
    # Fingerprint-related stuff
    #

    def _on_fingerprint_settings_changed(self, _settings, _key):
        if saldo.config_manager.get_fingerprint_quickunlock():
            logging.debug("Fingerprint got enabled, mapping fingerprint reader...")
            self._map_fingerprint_reader()
        else:
            logging.debug("Fingerprint got disabled, unmapping fingerprint reader...")
            self._unmap_fingerprint_reader()

    def _start_fingerprint_reader(self) -> None:
        if not self.fprint:
            return
        self.fprint.connect()
        status = self.fprint.verify_start()
        if status:
            self.fingerprint_img.props.visible = True

    def _stop_fingerprint_reader(self) -> None:
        if not self.fprint:
            return
        self.fprint.verify_stop()
        self.fprint.disconnect()
        self.fingerprint_img.props.visible = False

    def _map_fingerprint_reader(self) -> None:
        logging.debug("Connecting to the fingerprint device...")
        try:
            self.fprint = FingerprintVerifier(
                self._on_fingerprint_success,
                self._on_fingerprint_retry,
                self._on_fingerprint_failure,
            )
        except RuntimeError as err:
            logging.debug("Failed initialize fingerprint: %s", err)

    def _unmap_fingerprint_reader(self) -> None:
        if not self.fprint:
            return
        logging.debug("Disconnecting the fingerprint device...")
        self._stop_fingerprint_reader()
        self.fprint = None

    def _on_fingerprint_success(self):
        """Success callback of the the FingerprintVerifier."""
        ctx = self.fingerprint_img.get_style_context()
        ctx.add_class("success")
        GLib.timeout_add(250, self._after_fingerprint_success)
        logging.debug("Fingerprint success")

    def _after_fingerprint_success(self):
        """Unlocks database after animation of _on_fingerprint_success."""
        GLib.idle_add(self._load_database)

    def _on_fingerprint_retry(self):
        """Retry callback of the the FingerprintVerifier."""
        ctx = self.fingerprint_img.get_style_context()
        ctx.add_class("retry")
        GLib.timeout_add(850, self._after_fingerprint_retry)
        logging.debug("Fingerprint retry")

    def _after_fingerprint_retry(self):
        """Cleans up animation after _on_fingerprint_retry."""
        ctx = self.fingerprint_img.get_style_context()
        ctx.remove_class("retry")

    def _on_fingerprint_failure(self):
        """Failure callback of the the FingerprintVerifier."""
        ctx = self.fingerprint_img.get_style_context()

        self.fprint_tries += 1

        if self.fprint_tries >= self.FPRINT_MAX_TRIES:
            # at most 3 tries for unlocking with the fingerprint sensor
            ctx.add_class("error")
            self._window._send_notification(
                _("Maximum tries reached. Please unlock using the password."),
            )
            self.fprint.disconnect()
            logging.debug("Max tries reached. Fingerprint disabled.")
        else:
            # else start verification again
            ctx.add_class("warning")
            GLib.timeout_add(850, self._after_fingerprint_warning)
            logging.debug("Fingerprint no-match for try %i", self.fprint_tries)
            status = self.fprint.verify_start()
            if not status:
                # in case fingerprint sensor itself refuses
                ctx.add_class("error")
                self._window._send_notification(_("Please unlock using the password."))
                logging.debug(
                    "Retry after verify-no-match failed. Fingerprint disabled.",
                )

    def _after_fingerprint_warning(self):
        """Cleans up.

        Animation after _on_fingerprint_failure if FPRINT_MAX_TRIES not reached
        """
        ctx = self.fingerprint_img.get_style_context()
        ctx.remove_class("warning")

    def _reset_fingerprint_image(self):
        ctx = self.fingerprint_img.get_style_context()
        ctx.remove_class("warning")
        ctx.remove_class("success")
        ctx.remove_class("error")
        self.fprint_tries = 0
