from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
from PyQt6.QtCore import Qt, QAbstractListModel, QModelIndex

from electrum.logging import get_logger
from electrum.util import Satoshis
from electrum.interface import ServerAddr, PREFERRED_NETWORK_PROTOCOL
from electrum import blockchain

from .util import QtEventListener, qt_event_listener


class QEServerListModel(QAbstractListModel, QtEventListener):
    _logger = get_logger(__name__)

    # define listmodel rolemap
    _ROLE_NAMES=('name', 'address', 'is_connected', 'is_primary', 'is_tor', 'chain', 'height')
    _ROLE_KEYS = range(Qt.ItemDataRole.UserRole, Qt.ItemDataRole.UserRole + len(_ROLE_NAMES))
    _ROLE_MAP  = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
    _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS))

    def __init__(self, network, parent=None):
        super().__init__(parent)

        self._chaintips = 0
        self._servers = []

        self.network = network
        self.initModel()
        self.register_callbacks()
        self.destroyed.connect(lambda: self.unregister_callbacks())

    @qt_event_listener
    def on_event_network_updated(self):
        self._logger.info(f'network updated')
        self.initModel()

    @qt_event_listener
    def on_event_blockchain_updated(self):
        self._logger.info(f'blockchain updated')
        self.initModel()

    @qt_event_listener
    def on_event_default_server_changed(self):
        self._logger.info(f'default server changed')
        self.initModel()

    def rowCount(self, index):
        return len(self._servers)

    def roleNames(self):
        return self._ROLE_MAP

    def data(self, index, role):
        server = self._servers[index.row()]
        role_index = role - Qt.ItemDataRole.UserRole
        value = server[self._ROLE_NAMES[role_index]]

        if isinstance(value, (bool, list, int, str)) or value is None:
            return value
        if isinstance(value, Satoshis):
            return value.value
        return str(value)

    def clear(self):
        self.beginResetModel()
        self._servers = []
        self.endResetModel()

    chaintipsChanged = pyqtSignal()
    @pyqtProperty(int, notify=chaintipsChanged)
    def chaintips(self):
        return self._chaintips

    def get_chains(self):
        chains = self.network.get_blockchains()
        n_chains = len(chains)
        if n_chains != self._chaintips:
            self._chaintips = n_chains
            self.chaintipsChanged.emit()
        return chains

    @pyqtSlot()
    def initModel(self):
        self.clear()

        servers = []

        chains = self.get_chains()

        for chain_id, interfaces in chains.items():
            self._logger.debug(f'chain {chain_id} has {len(interfaces)} interfaces')
            b = blockchain.blockchains.get(chain_id)
            if b is None:
                continue

            name = b.get_name()

            self._logger.debug(f'chain {chain_id} has name={name}, max_forkpoint=@{b.get_max_forkpoint()}, height={b.height()}')

            for i in interfaces:
                server = {
                    'chain': name,
                    'chain_height': b.height(),
                    'is_primary': i == self.network.interface,
                    'is_connected': True,
                    'name': str(i.server),
                    'address': i.server.to_friendly_name(),
                    'height': i.tip
                }

                servers.append(server)

        # disconnected servers
        all_servers = self.network.get_servers()
        connected_hosts = set([iface.host for ifaces in chains.values() for iface in ifaces])
        protocol = PREFERRED_NETWORK_PROTOCOL
        for _host, d in sorted(all_servers.items()):
            if _host in connected_hosts:
                continue
            if _host.endswith('.onion') and not self.network.is_proxy_tor:
                continue
            port = d.get(protocol)
            if port:
                s = ServerAddr(_host, port, protocol=protocol)
                server = {
                    'chain': '',
                    'chain_height': 0,
                    'height': 0,
                    'is_primary': False,
                    'is_connected': False,
                    'name': s.net_addr_str()
                }
                server['address'] = server['name']

                servers.append(server)

        self.beginInsertRows(QModelIndex(), 0, len(servers) - 1)
        self._servers = servers
        self.endInsertRows()
