# Copyright 2021 Vincent Texier <vit@free.fr>
#
# This software 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 software 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/>.
import logging
import re
import sys
from typing import Optional

from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QFont, QMovie
from PyQt5.QtWidgets import QApplication, QDialog, QWidget

from tikka.domains.application import Application
from tikka.domains.entities.account import Account
from tikka.domains.entities.constants import DATA_PATH
from tikka.domains.entities.identity import Identity, IdentityStatus
from tikka.slots.pyqt.entities.constants import (
    ADDRESS_MONOSPACE_FONT_NAME,
    DEBOUNCE_TIME,
    ICON_LOADER,
)
from tikka.slots.pyqt.entities.event_bus import EventBus
from tikka.slots.pyqt.entities.worker import (
    ThreadManager,
)
from tikka.slots.pyqt.resources.gui.windows.identity_confirm_rc import (
    Ui_IdentityConfirmDialog,
)
from tikka.slots.pyqt.windows.account_unlock import AccountUnlockWindow


class IdentityConfirmWindow(QDialog, Ui_IdentityConfirmDialog):
    """
    IdentityConfirmWindow class
    """

    def __init__(
        self,
        application: Application,
        account: Account,
        parent: Optional[QWidget] = None,
    ):
        """
        Init identity confirm window

        :param application: Application instance
        :param account: Identity owner Account instance
        :param parent: QWidget instance
        """
        super().__init__(parent=parent)
        self.setupUi(self)

        self.application = application
        self._ = self.application.translator.gettext

        self.event_bus = EventBus.instance()

        self.account = account
        self.identity: Optional[Identity] = self.application.identities.get_by_address(
            account.address
        )

        # animated loading icon
        self.loader_movie = QMovie(ICON_LOADER)
        self.loader_movie.start()
        self.loaderIconLabel.setMovie(self.loader_movie)
        loader_icon_size_policy = self.loaderIconLabel.sizePolicy()
        loader_icon_size_policy.setRetainSizeWhenHidden(True)
        self.loaderIconLabel.setSizePolicy(loader_icon_size_policy)
        self.loaderIconLabel.hide()

        self.monospace_font = QFont(ADDRESS_MONOSPACE_FONT_NAME)
        self.monospace_font.setStyleHint(QFont.Monospace)
        self.accountValueLabel.setFont(self.monospace_font)

        # events
        self.nameLineEdit.textChanged.connect(self.on_name_line_edit_changed)
        self.confirmIdentityButton.clicked.connect(
            self._on_confirm_identity_button_clicked
        )

        ##############################
        # ASYNC METHODS
        ##############################
        self.thread_manager: ThreadManager = ThreadManager(self)

        # debounce timers
        self.name_debounce_timer = QTimer()
        self.name_debounce_timer.timeout.connect(self._name_debounce_call)

        self.confirmIdentityButton.setEnabled(False)
        self.update_ui()
        self.nameLineEdit.setFocus()

    def on_name_line_edit_changed(self) -> None:
        """
        Triggered when name line edit is changed

        :return:
        """
        if self.name_debounce_timer.isActive():
            self.name_debounce_timer.stop()
        self.name_debounce_timer.start(DEBOUNCE_TIME)

    def _name_debounce_call(self):
        """
        Debounce function triggered only after idle time when typing name

        :return:
        """
        # Stop name_debounce_timer to avoid infinite loop
        if self.name_debounce_timer.isActive():
            self.name_debounce_timer.stop()

        name = self.nameLineEdit.text().strip()

        # Check name validity
        # Criteria:
        # 1. Length <= 42 characters
        # 2. Only ASCII alphanumeric characters or '_' or '-'
        # 3. Cannot start or end with '-' or '_' (optional, comment if not needed)

        # Define the regex pattern
        # ^[a-zA-Z0-9] - must start with alphanumeric
        # [a-zA-Z0-9_-]* - zero or more alphanumeric, underscore, or hyphen
        # $ - end of string
        # Total length <= 42
        pattern = r"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,41}$"

        # Check if name is valid
        name_validity = bool(re.match(pattern, name)) and len(name) <= 42

        if not name_validity:
            self.errorLabel.setText(self._("Invalid name"))
            self.confirmIdentityButton.setEnabled(False)
        else:
            self.errorLabel.setText("")
            if self.identity.status == IdentityStatus.UNCONFIRMED:
                self.confirmIdentityButton.setEnabled(True)

    def update_ui(self):
        """
        Update UI fields

        :return:
        """
        self.accountValueLabel.setText(self.account.address)
        if self.identity is not None:
            display_identity_status = {
                IdentityStatus.UNCONFIRMED.value: self._("Unconfirmed"),
                IdentityStatus.UNVALIDATED.value: self._("Unvalidated"),
                IdentityStatus.MEMBER.value: self._("Member"),
                IdentityStatus.NOT_MEMBER.value: self._("Not member"),
                IdentityStatus.REVOKED.value: self._("Revoked"),
            }
            identity_status_color = {
                IdentityStatus.UNCONFIRMED.value: "orange",
                IdentityStatus.UNVALIDATED.value: "orange",
                IdentityStatus.MEMBER.value: "green",
                IdentityStatus.NOT_MEMBER.value: "red",
                IdentityStatus.REVOKED.value: "red",
            }
            identity_status_style = f"""
                                    color: white;
                                    border: 3px solid "{identity_status_color[self.identity.status.value]}";
                                    border-radius: 10%;
                                    background: "{identity_status_color[self.identity.status.value]}";
                                    """
            self.identityIndexValueLabel.setText(f"#{self.identity.index}")
            self.identityStatusValueLabel.setText(
                display_identity_status[self.identity.status.value]
            )
            self.identityStatusValueLabel.setStyleSheet(identity_status_style)

    def _on_confirm_identity_button_clicked(self):
        """
        Run when use click confirm identity button

        :return:
        """
        if not self.application.wallets.is_unlocked(self.account.address):
            # ask password...
            dialog_code = AccountUnlockWindow(
                self.application, self.account, self
            ).exec_()
            if dialog_code == QDialog.Rejected:
                return

        self.loaderIconLabel.show()
        self.confirmIdentityButton.setDisabled(True)
        self.thread_manager.start(
            self.network_confirm_identity, self._on_finished_network_confirm_identity
        )

    def network_confirm_identity(self):
        """
        Confirm identity on the network

        :return:
        """
        name = self.nameLineEdit.text().strip()
        try:
            self.application.identities.network_confirm(
                self.application.wallets.get_keypair(self.account.address),
                name,
            )
        except Exception as exception:
            self.errorLabel.setStyleSheet("color: red;")
            self.errorLabel.setText(str(exception))
            logging.exception(exception)
        else:
            self.errorLabel.setStyleSheet("color: green;")
            self.errorLabel.setText(
                self._("Identity {name}#{index} confirmed!").format(
                    name=name, index=self.identity.index
                )
            )
            try:
                self.application.identities.network_update_identity(
                    self.account.address
                )
            except Exception as exception:
                logging.exception(exception)

    def _on_finished_network_confirm_identity(self):
        """
        When async network_create_identity is finished

        :return:
        """
        self.identity = self.application.identities.get_by_address(self.account.address)
        self.confirmIdentityButton.setDisabled(False)
        self.loaderIconLabel.hide()
        self.update_ui()
        if self.identity.status == IdentityStatus.UNVALIDATED:
            self.confirmIdentityButton.setEnabled(False)
            self.event_bus.identity_confirmed.emit(self.identity)
            logging.debug(
                "identity_confirmed signal emitted from identity_confirm window %s#%s",
                self.identity.name,
                self.identity.index,
            )
        else:
            self.confirmIdentityButton.setDisabled(False)


if __name__ == "__main__":
    qapp = QApplication(sys.argv)
    application_ = Application(DATA_PATH)
    account_ = Account("xxxx")
    IdentityConfirmWindow(application_, account_).exec_()
