from functools import partial
from datetime import datetime
import pymoku
from pymoku.applets import doasync
from PySide6.QtWidgets import (QDialog, QVBoxLayout, QDialogButtonBox, QLabel, QProgressDialog,
                               QWidget, QTabWidget, QFormLayout, QLineEdit, QPushButton,
                               QProgressBar, QMessageBox, QCheckBox, QComboBox, QListWidget)
from PySide6.QtCore import QTimer, Qt


class MokuConfigDialog(QDialog):

    def __init__(self, parent, minfo):
        super().__init__(parent)
        self.minfo = minfo
        self.moku_skt = None
        self.to_apply = {}

        self.setWindowTitle('Device Config')
        self.progressbar = QProgressBar()
        self.progressbar.setMinimum(0)
        self.progressbar.setMaximum(0)

        self.lyt = QVBoxLayout()
        self.lyt.addWidget(self.progressbar)

        self.setLayout(self.lyt)

    def showEvent(self, event):
        self._load_config()
        super().showEvent(event)

    def closeEvent(self, event):
        if self._scan_poll:
            self._scan_poll.stop()
        if self.moku_skt:
            self.moku_skt.close()
        super().closeEvent(event)

    def _load_config(self):
        @doasync
        def _async():
            self.moku_skt = pymoku._try_connect(
                pymoku.ZMQCurveSocketFactory(self.minfo.ip_addr), fw_ver=self.minfo.fwver)
            config = self.moku_skt.get_property('')
            return config

        _async.result.connect(self._create_tabs)
        _async.error.connect(self.showerror)
        _async()

    def _create_tabs(self, config):
        self.progressbar.setVisible(False)

        tabs = QTabWidget()
        tabs.addTab(self._device_tab(config), 'Device')
        tabs.addTab(self._wifi_tab(config), 'Wifi')
        tabs.addTab(self._ap_tab(config), 'Access Point')
        tabs.addTab(self._ethernet_tab(config), 'Ethernet')
        tabs.addTab(self._license_tab(config), 'License')
        self.lyt.addWidget(tabs)
        buttonBox = QDialogButtonBox(QDialogButtonBox.Apply | QDialogButtonBox.Close)
        buttonBox.button(QDialogButtonBox.Apply).clicked.connect(self.apply)
        buttonBox.button(QDialogButtonBox.Close).clicked.connect(self.reject)
        self.lyt.addWidget(buttonBox)

    def showerror(self, exc):
        QMessageBox.critical(self, str(exc), str(exc.args[0]))

    def _set_value(self, prop, value):
        self.to_apply[prop] = value

    def _set_bool(self, prop, value):
        self.to_apply[prop] = bool(value)

    def _device_tab(self, config):
        wdgt = QWidget()
        lyt = QFormLayout()
        dev_conf = config['device']
        lyt.addRow('Hardware Version:', QLabel(f"{dev_conf['hw_version']}"))
        lyt.addRow('Batch Number:', QLabel(f"{dev_conf['batch']}"))
        lyt.addRow('Serial:', QLabel(f"{dev_conf['serial']}"))
        lyt.addRow('Firmware Version:', QLabel(f"{dev_conf['fw_version']}"))
        if 'hw_variant' in dev_conf:
            lyt.addRow('Variant:', QLabel(dev_conf['hw_variant']))
        lyt.addRow('Colour:', QLabel(dev_conf['colour']))
        lyt.addRow('Calibration Date:', QLabel(dev_conf['cal_date']))
        name_edit = QLineEdit(config['system']['name'])
        lyt.addRow('Device Name:', name_edit)
        lyt.addRow('Boot Mode:', QLabel(config['system']['bootmode']))
        lyt.addRow('Time Zone:', QLabel(config['system']['timezone']))
        lyt.addRow('Date/Time:', QLabel(config['system']['datetime']))
        time_btn = QPushButton('Sync Time')
        time_btn.clicked.connect(self._sync_time)
        lyt.addRow('', time_btn)

        name_edit.textEdited.connect(partial(self._set_value, 'system.name'))

        wdgt.setLayout(lyt)
        return wdgt

    def _wifi_tab(self, config):
        self._scan_poll = None

        wdgt = QWidget()
        lyt = QFormLayout()

        wifi_conf = config['network']['wifi']

        enable_check = QCheckBox()
        enable_check.setChecked(wifi_conf['enabled'])
        lyt.addRow('Enabled:', enable_check)
        enable_check.stateChanged.connect(partial(self._set_bool, 'network.wifi.enabled'))

        ssid_combo = QComboBox()
        ssid_combo.setEditable(True)
        ssid_combo.setMinimumContentsLength(20)
        ssid_combo.lineEdit().setText(f"{wifi_conf['ssid']}")
        lyt.addRow('SSID:', ssid_combo)
        ssid_combo.editTextChanged.connect(partial(self._set_value, 'network.wifi.ssid'))
        self.ssid_combo = ssid_combo

        self._update_ssid_list(config['network'].get('avail', []))

        scan_btn = QPushButton('Scan')
        scan_btn.clicked.connect(self._scan_wifi)
        lyt.addRow('', scan_btn)

        psk_edit = QLineEdit(f"{wifi_conf['psk']}")
        lyt.addRow('Password:', psk_edit)
        psk_edit.textEdited.connect(partial(self._set_value, 'network.wifi.psk'))

        dhcp_check = QCheckBox()
        dhcp_check.setChecked(wifi_conf['dhcp'])
        lyt.addRow('DHCP:', dhcp_check)
        dhcp_check.stateChanged.connect(partial(self._set_bool, 'network.wifi.dhcp'))

        addr_edit = QLineEdit(f"{wifi_conf['addr']}")
        lyt.addRow('IP Address:', addr_edit)
        addr_edit.textEdited.connect(partial(self._set_value, 'network.wifi.addr'))

        netmask_edit = QLineEdit(f"{wifi_conf['netmask']}")
        lyt.addRow('Subnet Mask:', netmask_edit)
        netmask_edit.textEdited.connect(partial(self._set_value, 'network.wifi.netmask'))

        nmode_check = QCheckBox()
        nmode_check.setChecked(wifi_conf['nmode'])
        lyt.addRow('N Mode:', nmode_check)
        nmode_check.stateChanged.connect(partial(self._set_bool, 'network.wifi.nmode'))

        wdgt.setLayout(lyt)

        return wdgt

    def _ap_tab(self, config):
        wdgt = QWidget()
        lyt = QFormLayout()

        ap_conf = config['network']['ap']

        enable_check = QCheckBox()
        enable_check.setChecked(ap_conf['enabled'])
        lyt.addRow('Enabled:', enable_check)
        enable_check.stateChanged.connect(partial(self._set_bool, 'network.ap.enabled'))

        ssid_edit = QLineEdit(f"{ap_conf['ssid']}")
        lyt.addRow('SSID:', ssid_edit)
        ssid_edit.textEdited.connect(partial(self._set_value, 'network.ap.ssid'))

        psk_edit = QLineEdit(f"{ap_conf['psk']}")
        lyt.addRow('Password:', psk_edit)
        psk_edit.textEdited.connect(partial(self._set_value, 'network.ap.psk'))

        addr_edit = QLineEdit(f"{ap_conf['addr']}")
        lyt.addRow('IP Address:', addr_edit)
        addr_edit.textEdited.connect(partial(self._set_value, 'network.ap.addr'))

        netmask_edit = QLineEdit(f"{ap_conf['netmask']}")
        lyt.addRow('Subnet Mask:', netmask_edit)
        netmask_edit.textEdited.connect(partial(self._set_value, 'network.ap.netmask'))

        nmode_check = QCheckBox()
        nmode_check.setChecked(ap_conf['nmode'])
        lyt.addRow('N Mode:', nmode_check)
        nmode_check.stateChanged.connect(partial(self._set_bool, 'network.ap.nmode'))

        wdgt.setLayout(lyt)
        return wdgt

    def _ethernet_tab(self, config):
        wdgt = QWidget()
        lyt = QFormLayout()

        eth_conf = config['network']['eth']

        enable_check = QCheckBox()
        enable_check.setChecked(eth_conf['enabled'])
        lyt.addRow('Enabled:', enable_check)
        enable_check.stateChanged.connect(partial(self._set_bool, 'network.eth.enabled'))

        dhcp_check = QCheckBox()
        dhcp_check.setChecked(eth_conf['dhcp'])
        lyt.addRow('DHCP:', dhcp_check)
        dhcp_check.stateChanged.connect(partial(self._set_bool, 'network.eth.dhcp'))

        addr_edit = QLineEdit(f"{eth_conf['addr']}")
        lyt.addRow('IP Address:', addr_edit)
        addr_edit.textEdited.connect(partial(self._set_value, 'network.eth.addr'))

        netmask_edit = QLineEdit(f"{eth_conf['netmask']}")
        lyt.addRow('Subnet Mask:', netmask_edit)
        netmask_edit.textEdited.connect(partial(self._set_value, 'network.eth.netmask'))

        wdgt.setLayout(lyt)
        return wdgt

    def _license_tab(self, config):
        wdgt = QWidget()
        lyt = QVBoxLayout()

        list_wdgt = QListWidget()

        lyt.addWidget(list_wdgt)

        items = config['license']['entitlements'].values()
        list_wdgt.addItems(items)

        refresh_btn = QPushButton('Refresh')
        refresh_btn.clicked.connect(self._refresh_licences)
        refresh_btn.setEnabled(False)
        lyt.addWidget(refresh_btn)

        wdgt.setLayout(lyt)
        return wdgt

    def apply(self):
        self.write_config(self.to_apply)
        self.to_apply = {}

    def write_config(self, props):
        if not props:
            return

        @doasync
        def _async():
            self.moku_skt.set_properties(list(props.items()))

        _async.error.connect(self.showerror)
        _async()

    @doasync
    def _sync_time(self):
        self.moku_skt.set_property('system.datetime', datetime.utcnow().isoformat())

    def _scan_wifi(self):
        enabled = self.moku_skt.get_property('network.wifi.enabled')
        if not enabled:
            ret = QMessageBox.question(self,
                                       "Enable Wifi?",
                                       "Wifi must be enabled to perform SSID scan. Enable?",
                                       QMessageBox.Yes | QMessageBox.No)
            if ret == QMessageBox.Yes:
                QProgressDialog("Enabling Wifi...", "", 0, 0, self, Qt.WindowModal)
                self.moku_skt.set_property('network.wifi.enabled', True)
            else:
                return

        if self._scan_poll is None:
            self._scan_poll = QTimer(self)
            self._scan_poll.timeout.connect(self._update_ssids)
            self._scan_poll.start(5000)

        self._update_ssids()

    def _update_ssid_list(self, ssids):
        ssids.sort(reverse=True, key=lambda x: x[1])
        self.ssid_combo.clear()
        self.ssid_combo.addItems([name for name, strength in ssids])

    def _update_ssids(self):
        @doasync
        def _async():
            self.moku_skt.set_property('network.avail', True)
            ssids = self.moku_skt.get_property('network.avail')
            return ssids

        _async.result.connect(self._update_ssid_list)

    def _refresh_licences(self):
        print('refresh')
