# 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 __future__ import annotations
import logging
from enum import IntEnum
from gettext import gettext as _
from gi.repository import Gdk, Gtk, Adw, Gio, GLib

import saldo.config_manager
from saldo.widgets.clients import Clients
from saldo.backend.backend import BankingBackend
from saldo.widgets.create_safe_page import CreateSafePage  # noqa
from saldo.widgets.locked_page import LockedPage  # noqa
from saldo.widgets.transfer import Transfer
from saldo.widgets.change_password_dialog import ChangePasswordDialog

from saldo.widgets.unlocked_page import UnlockedPage  # noqa
from saldo.widgets.welcome_page import WelcomePage  # noqa
from saldo.settings_dialog import SettingsDialog  # noqa
from saldo.backend.transaction_data import TransactionData
from saldo.backend.helper import readable_date


@Gtk.Template(resource_path="/org/tabos/saldo/ui/window.ui")
class Window(Adw.ApplicationWindow):
    class View(IntEnum):
        WELCOME = 0
        CREATE_SAFE = 1
        LOCKED = 2
        UNLOCKED = 3

    __gtype_name__ = "Window"

    _main_view = Gtk.Template.Child()
    _welcome_bin = Gtk.Template.Child()
    _create_safe_bin = Gtk.Template.Child()
    _locked_bin = Gtk.Template.Child()
    _unlocked_bin = Gtk.Template.Child()
    _toast_overlay = Gtk.Template.Child()
    _lock_timer = None
    _view = View.WELCOME

    _automatic_refresh_id = 0

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.assemble_window()
        self.setup_actions()

        self.backend = BankingBackend()

        if saldo.config_manager.get_automatic_refresh():
            self.start_automatic_refresh()

        if not self.backend.database_exists:
            self.view = self.View.WELCOME
        else:
            self.view = self.View.LOCKED

        self.backend.connect("new-transaction", self._on_new_transaction)

    def _on_new_transaction(
        self,
        backend: BankingBackend,
        transaction: TransactionData,
        known_applicant: bool,
    ) -> None:
        title = _("New transaction")

        if transaction.amount < 0.0:
            body = _("You paid %.2f %s to %s (%s).") % (
                abs(round(transaction.amount, 2)),
                transaction.currency,
                transaction.name,
                readable_date(transaction.date),
            )
            if not known_applicant:
                title = _("Attention: ") + title

        else:
            body = _("You have received %.2f %s from %s (%s).") % (
                abs(round(transaction.amount, 2)),
                transaction.currency,
                transaction.name,
                readable_date(transaction.date),
            )

        self._send_desktop_notification(title, body, not known_applicant)

    def _send_desktop_notification(
        self, title: str, body: str, high_prio: bool
    ) -> None:
        notification = Gio.Notification.new(title)
        notification.set_body(body)
        if high_prio:
            notification.set_priority(Gio.NotificationPriority.HIGH)
        Gio.Application.get_default().send_notification(None, notification)

    def assemble_window(self):
        window_size = saldo.config_manager.get_window_size()
        self.set_default_size(window_size[0], window_size[1])

        self._init_style()

    def setup_actions(self):
        action = Gio.SimpleAction.new("create_safe", None)
        action.connect("activate", self._on_create_safe)
        self.add_action(action)

        action = Gio.SimpleAction.new("refresh", None)
        action.connect("activate", self._on_refresh)
        self.add_action(action)

        action = Gio.SimpleAction.new("lock", None)
        action.connect("activate", self._on_lock)
        self.add_action(action)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self._on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("clients", None)
        action.connect("activate", self._on_clients)
        self.add_action(action)

        action = Gio.SimpleAction.new("settings", None)
        action.connect("activate", self._on_settings)
        self.add_action(action)

        action = Gio.SimpleAction.new("transfer", None)
        action.connect("activate", self._on_transfer)
        self.add_action(action)

        action = Gio.SimpleAction.new("change_password", None)
        action.connect("activate", self._on_change_password)
        self.add_action(action)

        action = Gio.SimpleAction.new_stateful(
            "run-in-background", None, GLib.Variant.new_boolean(True)
        )
        action.set_state(
            GLib.Variant.new_boolean(saldo.config_manager.get_run_in_background())
        )
        action.connect("change-state", self._on_run_in_background)
        self.add_action(action)

    def _on_run_in_background(self, action, *args):
        is_run_in_background = not saldo.config_manager.get_run_in_background()
        action.set_state(GLib.Variant.new_boolean(is_run_in_background))
        self.hide_on_quit(is_run_in_background)
        saldo.config_manager.set_run_in_background(is_run_in_background)

    def _send_notification(self, notification: str) -> None:
        toast = Adw.Toast.new(notification)
        self._toast_overlay.add_toast(toast)

    def _on_refresh(self, *_):
        if self.view != self.View.UNLOCKED:
            return

        self.backend.refresh_accounts()

    def _on_lock(self, *_):
        if self.view != self.View.UNLOCKED:
            return

        self.view = self.View.LOCKED

    def _on_about(self, _action: Gio.Action, _param: GLib.Variant) -> None:
        """Invoked when we click "about" in the main menu"""
        builder = Gtk.Builder.new_from_resource("/org/tabos/saldo/ui/about_dialog.ui")
        about_dialog = builder.get_object("about_dialog")
        about_dialog.set_transient_for(self)
        about_dialog.present()

    def _on_transfer(self, *_):
        if self.view != self.View.UNLOCKED:
            return

        transfer = Transfer(self.backend)
        transfer.present(self)

    def _on_change_password(self, *_):
        if self.view != self.View.UNLOCKED:
            return

        change_password = ChangePasswordDialog(self.backend, use_header_bar=True)
        change_password.set_transient_for(self)
        change_password.show()

    def _on_settings(self, *_):
        if self.view != self.View.UNLOCKED:
            return

        settings = SettingsDialog(self)
        settings.present(self)

    def _init_style(self):
        style_provider = Gtk.CssProvider()
        style_provider.load_from_resource("/org/tabos/saldo/org.tabos.saldo.css")
        Gtk.StyleContext.add_provider_for_display(
            Gdk.Display.get_default(),
            style_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
        )

        self.apply_theme()

    def apply_theme(self) -> None:
        manager = Adw.StyleManager.get_default()
        if manager.props.system_supports_color_schemes:
            dark_theme = saldo.config_manager.get_dark_theme()
            if dark_theme:
                manager.props.color_scheme = Adw.ColorScheme.PREFER_DARK
            else:
                manager.props.color_scheme = Adw.ColorScheme.DEFAULT

    def _on_create_safe(self, *_):
        self.view = self.View.CREATE_SAFE

    def _on_clients(self, *_):
        clients = Clients(backend=self.backend)

        clients.present(self)

    def _lock_timeout(self):
        self._lock_timer = None
        self.view = self.View.LOCKED
        self._send_notification(_("Safe locked due to inactivity"))

    def _stop_lock_timer(self):
        if self._lock_timer:
            GLib.source_remove(self._lock_timer)
            self._lock_timer = None

    def start_lock_timer(self):
        self._stop_lock_timer()

        timeout = saldo.config_manager.get_lock_timer_seconds()
        self._lock_timer = GLib.timeout_add_seconds(timeout, self._lock_timeout)

    def do_close_request(self):  # pylint: disable=arguments-differ
        self.save_window_size()

        if saldo.config_manager.get_run_in_background():
            self.hide()
            return True

        self._stop_lock_timer()
        return False

    @property
    def view(self) -> View:
        return self._view

    @view.setter  # type: ignore
    def view(self, new_view: View) -> None:
        stack = self._main_view

        self._view = new_view

        if new_view == self.View.WELCOME:
            if not self._welcome_bin.props.child:
                welcome = WelcomePage(self)
                self._welcome_bin.props.child = welcome

            stack.props.visible_child_name = "welcome"
            self._welcome_bin.props.child._create_safe_button.grab_focus()
        elif new_view == self.View.CREATE_SAFE:
            if not self._create_safe_bin.props.child:
                create_safe = CreateSafePage(self, self.backend)
                self._create_safe_bin.props.child = create_safe

            stack.props.visible_child_name = "create_safe"
            self._create_safe_bin.props.child._safe_password1.grab_focus()
        elif new_view == self.View.LOCKED:
            if not self._locked_bin.props.child:
                locked = LockedPage(self)
                self._locked_bin.props.child = locked

            stack.props.visible_child_name = "locked"
            self._locked_bin.props.child._password_entry.grab_focus()
        elif new_view == self.View.UNLOCKED:
            if not self._unlocked_bin.props.child:
                unlocked = UnlockedPage(self, self.backend)
                self._unlocked_bin.props.child = unlocked

            stack.props.visible_child_name = "unlocked"
            self.start_lock_timer()

    def _automatic_refresh(self):
        self.backend.refresh_accounts()
        return GLib.SOURCE_CONTINUE

    def start_automatic_refresh(self):
        self._automatic_refresh_id = GLib.timeout_add_seconds(
            15 * 60, self._automatic_refresh
        )

    def stop_automatic_refresh(self):
        if self._automatic_refresh_id:
            GLib.source_remove(self._automatic_refresh_id)

    def save_window_size(self):
        width = self.get_width()
        height = self.get_height()
        logging.debug("Saving window geometry: (%s, %s)", width, height)
        saldo.config_manager.set_window_size([width, height])

    def show_banner(self, label: str) -> None:
        if self.view == self.View.LOCKED:
            self._locked_bin.props.child.show_banner(label)
        elif self.view == self.View.CREATE_SAFE and (create_view := self.create_safe):
            create_view.show_banner(label)

    def close_banner(self):
        if self.view == self.View.LOCKED:
            self._locked_bin.props.child.close_banner()
        elif self.view == self.View.CREATE_SAFE and (create_view := self.create_safe):
            create_view.close_banner()
