import sys
import time
import weakref
from datetime import datetime

from lib.helpers import timing
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtGui import QColor, QPalette, QPixmap
from ui import Dialog
from ui.test_admin_permission import Test_Admin_Permission
from ui.widget import Widget


class Test_Test(Widget):
    ok = pyqtSignal(object)
    ko = pyqtSignal(object)

    def __init__(self, components=None, recipe=None, step=None, pieces=None, run_once=False, reset_on_start=True, enable_override=True):
        super().__init__()
        self.components = components
        if components is not None:
            if "leak_disabled" in self.components.keys():
                self.tester_component = None
            elif "tecna_t3"in self.components.keys():
                self.tester_component = "tecna_t3"
            else:
                self.tester_component = "furness_controls"

        self.recipe = recipe
        self.step = step
        self.pieces = pieces
        self.run_once = run_once
        self.reset_on_start = reset_on_start
        self.enable_override = enable_override
        # setup variables
        self.ok_counter = 0
        self.ok_counter_limit = 1
        # setup controls
        self.done = False
        self.done_ok = None
        self.ok_timer = QTimer()
        self.ok_timer.setSingleShot(True)
        self.ok_timer.setInterval(1000)
        self.ok_timer.timeout.connect(self.emit_ok)
        self.ko_timer = QTimer()
        self.ko_timer.setSingleShot(True)
        self.ko_timer.setInterval(1000)
        self.ko_timer.timeout.connect(self.emit_ko)
        # setup save frame button
        self.last = None
        if hasattr(self, "save_b"):
            self.save_b.setEnabled(False)
            self.save_b.clicked.connect(self.save_last)
        # setup override
        self.admin_challenged = False
        if hasattr(self, "override_b"):
            if self.enable_override:
                self.override_b.setVisible(True)
                self.override_b.setEnabled(True)
            else:
                self.override_b.setVisible(False)
                self.override_b.setEnabled(False)
            self.override_b.clicked.connect(self.override)
        # setup vision staus gui
        self.parent_assembly_widget = None
        self.status_imgs_full = {
            True: QPixmap("src/ui/imgs/success.png"),
            "": QPixmap("src/ui/imgs/neo.ico"),
            "warning": QPixmap("src/ui/imgs/warning.png"),
            "wait": QPixmap("src/ui/imgs/wait.png"),
            "calibrated-leak": QPixmap("src/ui/imgs/calibrated-leak.png"),
            "calibrated-leak-remove": QPixmap("src/ui/imgs/calibrated-leak-remove.png"),
            False: QPixmap("src/ui/imgs/fail.png"),
            None: QPixmap("src/ui/imgs/wait.png"),
        }
        self.status_imgs_small = {k: i.scaled(32, 32, Qt.KeepAspectRatio, Qt.SmoothTransformation) for k, i in self.status_imgs_full.items()}
        self.status_palettes = {
            True: QPalette(),
            "": QPalette(),
            "warning": QPalette(),
            False: QPalette(),
            None: QPalette(),
        }
        self.status_palettes[True].setColor(QPalette.Base, Qt.green)
        self.status_palettes[False].setColor(QPalette.Base, Qt.red)
        self.status_palettes["warning"].setColor(QPalette.Base, QColor(255, 165, 0))
        self.status_palettes[""].setColor(QPalette.Base, QColor(255, 255, 0))
        self.reset()
        self.visualize()

    def set_parent_assembly_widget(self, parent_assembly_widget=None):
        if parent_assembly_widget is None:
            self.parent_assembly_widget = None
        elif type(parent_assembly_widget) is weakref.ReferenceType:
            self.parent_assembly_widget = parent_assembly_widget
        else:
            self.parent_assembly_widget = weakref.ref(parent_assembly_widget)

    def start(self, recipe=None, step=None, pieces=None):
        self.admin_challenged = False
        self.recipe = recipe
        self.step = step
        self.pieces = pieces
        if self.run_once is True and self.done is True and self.done_ok is True:
            return False
        if self.reset_on_start:
            self.reset()
        if self.enable_override:
            self.override_b.setVisible(True)
            self.override_b.setEnabled(True)
        elif hasattr(self, "override_b"):
            self.override_b.setVisible(False)
            self.override_b.setEnabled(False)
        self.visualize()
        # start test
        self.start_time = timing()
        # TESTING
        if "--test" in sys.argv:
            if self.enable_override and hasattr(self, "_test_overridden_test_"):
                del self._test_overridden_test_
        # /TESTING
        return True

    def stop(self):
        pass

    def reset(self):
        self.ok_counter = 0
        self.done = False
        self.done_ok = None
        self.last = None

    def get(self, data=None, override=False, fail=False, preserve_counter=False, skip_delay=False):
        cur_timing = timing()
        if self.done:  # avoid proccessing if completed
            return
        if data is None:
            data = self.last if self.last is not None else {}
        else:
            data_usable = [d is not None for d in reversed(data)]
            if not any(data_usable):
                data = self.last if self.last is not None else {}
            else:
                data = data[-data_usable.index(True) - 1]
        data["time"] = datetime.now().strftime("%H:%M:%S")
        if fail:
            result_ok = False
        elif override:
            result_ok = True
        else:
            result_ok = data.get("results", {}).get("ok", None)
        duration=cur_timing - self.start_time
        data.pop("step", None)
        self.last = {
            **data,
            "overridden": override,
            "duration": float(f"{duration:.2f}"),
            "ok": result_ok,
        }
        if fail:
            self.ok_counter = 0
        elif override:
            self.ok_counter = self.ok_counter_limit
        elif result_ok is True:
            self.ok_counter += 1
        elif not preserve_counter:
            self.ok_counter = 0
        # check if completed
        if fail:
            self.done = True
            self.done_ok = False
            if hasattr(self, "override_b"):
                self.override_b.setEnabled(False)
            self.stop()
            if skip_delay:
                self.emit_ko()
            else:
                self.ko_timer.start()
        elif self.ok_counter >= self.ok_counter_limit:
            self.done = True
            self.done_ok = True
            if hasattr(self, "override_b"):
                self.override_b.setEnabled(False)
            self.stop()
            if skip_delay:
                self.emit_ok()
            else:
                self.ok_timer.start()
        self.visualize(self.last)
        # TESTING
        if "--test" in sys.argv:
            if self.enable_override and not hasattr(self, "_test_overridden_test_"):
                self._test_overridden_test_ = True
                self.override_b.click()
        # /TESTING

    def visualize(self, data=None, img=None):
        if data is None:
            data = {}
        overridden = data.get("overridden", False)
        results = data.get("results", None)
        if hasattr(self, "save_b"):
            self.save_b.setEnabled(self.last is not None)
        if hasattr(self, "state_l"):
            if overridden:
                self.state_l.setPixmap(self.status_imgs_small["warning"])
            elif results is None:
                self.state_l.setPixmap(self.status_imgs_small[None])
            elif results.get("ok", None) is True:
                self.state_l.setPixmap(self.status_imgs_small[True])
            else:
                self.state_l.setPixmap(self.status_imgs_small[False])
        if hasattr(self, "ok_counter_pb"):
            self.ok_counter_pb.setMaximum(self.ok_counter_limit)
            self.ok_counter_pb.setValue(min(self.ok_counter, self.ok_counter_limit))
            if self.ok_counter >= self.ok_counter_limit:
                self.ok_counter_pb.setPalette(self.status_palettes[True])
            else:
                self.ok_counter_pb.setPalette(self.status_palettes[False])
        if hasattr(self, "img_l"):
            if img is None:
                if overridden:
                    self.img = self.status_imgs_full["warning"]
                elif results is None or results.get("ok", None) is None:
                    self.img = self.status_imgs_full[None]
                elif results.get("ok", None) is False:
                    self.img = self.status_imgs_full[False]
                else:
                    self.img = self.status_imgs_full[None]
            else:
                self.img = img
        self.resizeEvent()

    def save_last(self):
        if self.last is None:
            return

    def resizeEvent(self, event=None):
        if hasattr(self, "img_l"):
            self.img_l.setPixmap(self.img.scaled(self.img_l.width(), self.img_l.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation))

    def challenge_admin(self, info):
        if not self.admin_challenged:
            d = Dialog(parent=self)
            d.setCentralWidget(Test_Admin_Permission(info))
            d.setModal(True)
            d.show()
            d.centralWidget.password_le.setFocus()
            r = d.exec()
            if r == d.Accepted:
                self.admin_challenged = True
            elif r == d.Rejected:
                self.admin_challenged ^= False
            else:
                raise AssertionError("Bad admin challenge dialog return code")
        return self.admin_challenged

    def override(self):
        if self.challenge_admin("Si sta tentando di bypassare il test"):
            self.get(override=True)

    def emit_ok(self):
        self.ok.emit(self.last)

    def emit_ko(self):
        self.ko.emit(self.last)
