# 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 sys
from typing import List, Optional

from PyQt5.QtCore import QPoint, QTimer
from PyQt5.QtWidgets import QApplication, QListView, QMainWindow, QWidget
from scalecodec import ss58_decode

from tikka.domains.application import Application
from tikka.domains.entities.account import SearchAccount
from tikka.domains.entities.constants import TEST_DATA_PATH
from tikka.slots.pyqt.entities.constants import ACCOUNT_SEARCH_LIMIT, DEBOUNCE_TIME
from tikka.slots.pyqt.entities.event_bus import EventBus
from tikka.slots.pyqt.entities.worker import ThreadManager
from tikka.slots.pyqt.models.account_search import (
    AccountSearchItemDelegate,
    AccountSearchListModel,
)
from tikka.slots.pyqt.resources.gui.widgets.account_search_rc import (
    Ui_SearchAccountWidget,
)
from tikka.slots.pyqt.widgets.account_search_menu import AccountSearchPopupMenu


class AccountSearchWidget(QWidget, Ui_SearchAccountWidget):
    """
    AccountSearchWidget class
    """

    def __init__(
        self,
        application: Application,
        parent: Optional[QWidget] = None,
    ) -> None:
        """
        Init AccountSearchWidget instance

        :param application: Application 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.event_bus.currency_unit_changed.connect(
            self.on_currency_unit_changed_event_bus
        )

        self.pattern = self.searchLineEdit.text().strip()
        self.accounts: List[SearchAccount] = []
        self.search_limit = ACCOUNT_SEARCH_LIMIT

        self.account_search_model = AccountSearchListModel(application, self.locale())
        self.listView.setModel(self.account_search_model)
        self.listView.setItemDelegate(AccountSearchItemDelegate())
        self.listView.setResizeMode(QListView.Adjust)

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

        # events
        self.searchLineEdit.textChanged.connect(self.on_search_line_edit_changed)
        self.searchButton.clicked.connect(self.on_search_button_clicked)
        self.listView.customContextMenuRequested.connect(self.on_search_context_menu)

        # debounce timers
        self.pattern_debounce_timer = QTimer()
        self.pattern_debounce_timer.timeout.connect(self._pattern_debounce_call)

    def on_search_line_edit_changed(self):
        """
        When user type text in line edit

        :return:
        """
        self.pattern = self.searchLineEdit.text().strip()
        if self.pattern_debounce_timer.isActive():
            self.pattern_debounce_timer.stop()
        self.pattern_debounce_timer.start(DEBOUNCE_TIME)

    def _pattern_debounce_call(self):
        """
        Debounce function triggered only after idle time when typing pattern

        :return:
        """
        # stop mnemonic_debounce_timer to avoid infinite loop
        if self.pattern_debounce_timer.isActive():
            self.pattern_debounce_timer.stop()

        self.thread_manager.start(
            self.fetch_accounts_from_network,
            self.on_finished_fetch_accounts_from_network,
        )

    def on_search_button_clicked(self):
        """
        When user click on search button

        :return:
        """
        self.thread_manager.start(
            self.fetch_accounts_from_network,
            self.on_finished_fetch_accounts_from_network,
        )

    def on_currency_unit_changed_event_bus(self):
        """
        Triggered when user change money units

        :return:
        """
        self.account_search_model.init_data(self.accounts, self.search_limit)

    def fetch_accounts_from_network(self):
        """
        Get transfers and UDs data from the network

        :return:
        """
        if not self.application.connections.indexer.is_connected():
            return
        try:
            self.accounts = self.application.accounts.network_search(
                self.pattern, self.search_limit
            )
        except Exception as exception:
            logging.exception(exception)

        # filter address with bad ss58 prefix stored in genesis
        ss58_prefix = self.application.currencies.get_current().ss58_format
        filtered_accounts = []
        for search_account in self.accounts:
            try:
                public_key = ss58_decode(search_account.address, ss58_prefix)
            except Exception:
                continue
            if len(public_key) == 64:
                filtered_accounts.append(search_account)
        self.accounts = filtered_accounts

    def on_finished_fetch_accounts_from_network(self):
        """
        Triggered when async request fetch_accounts_from_network is finished

        :return:
        """
        self.account_search_model.init_data(self.accounts, self.search_limit)
        logging.debug("Finished fetch accounts from search pattern %s", self.pattern)

    def on_search_context_menu(self, position: QPoint):
        """
        When right button on search listview

        :return:
        """
        # get selected search account
        search_account = self.listView.currentIndex().internalPointer()
        if search_account is not None and isinstance(search_account, SearchAccount):
            # display popup menu at click position
            menu = AccountSearchPopupMenu(self.application, search_account, self)
            menu.exec_(self.listView.mapToGlobal(position))


if __name__ == "__main__":
    from tikka.domains.entities.indexer import Indexer

    logging.basicConfig(level=logging.DEBUG)
    qapp = QApplication(sys.argv)

    application_ = Application(TEST_DATA_PATH)
    application_.connections.indexer.connect(
        Indexer(url="http://localhost:8081/v1/graphql")
    )
    application_.currencies.init_currency_repository("gdev")
    main_window = QMainWindow()
    main_window.showMaximized()

    main_window.setCentralWidget(AccountSearchWidget(application_, main_window))

    sys.exit(qapp.exec_())
