#  Copyright (c) Kuba Szczodrzyński 2023-5-13.

import json
import os
from logging import debug, error, info, warning
from os.path import dirname, isfile

import wx
import wx.adv
import wx.xrc
from ltchiptool.gui.panels.base import BasePanel
from ltchiptool.gui.utils import on_event
from ltchiptool.util.logging import LoggingHandler

from upk2esphome import Opts, generate_yaml

from .common import cloudcutter_get_device, cloudcutter_list_devices
from .work import UpkThread

DISCLAIMER_TEXT = """
While the author has taken care to write the converter as well as possible, keep in mind that this might not be 100% accurate!

There may be some errors, such as missing components (unsupported types) or incorrect readings.

This serves mostly as a kickstart config, rather than a production-ready file. Make sure to review the output before uploading it to the device.

**We do not take responsibility for using this tool and the generated configs.**
"""


class UpkPanel(BasePanel):
    last_dir: str = None
    last_url: str = None
    logs_shown: bool = False
    disclaimer_shown: bool = False

    def __init__(self, parent: wx.Window, frame):
        super().__init__(parent, frame)
        self.LoadXRCFile("upk2esphome.xrc")
        self.LoadXRC("UpkPanel")
        self.AddToNotebook("UPK2ESPHome")

        self.Notebook: wx.Notebook = self.FindWindowByName("notebook_upk", self)

        self.BindButton("button_kickstart", self.OnDoKickstartClick)
        self.BindButton("button_cloudcutter", self.OnDoCloudcutterClick)
        self.BindButton("button_dump", self.OnDoDumpClick)

        self.Opts: dict[str, wx.CheckBox | wx.TextCtrl] = {
            "esphome_block": self.BindCheckBox("opts_esphome_block"),
            "name_mac": self.BindCheckBox("opts_name_mac"),
            "common": self.BindCheckBox("opts_common"),
            "web_server": self.BindCheckBox("opts_web_server"),
            "restart": self.BindCheckBox("opts_restart"),
            "uptime": self.BindCheckBox("opts_uptime"),
            "lt_version": self.BindCheckBox("opts_lt_version"),
            "wifi_ssid": self.BindTextCtrl("opts_wifi_ssid"),
            "wifi_password": self.BindTextCtrl("opts_wifi_password"),
            "ota_password": self.BindTextCtrl("opts_ota_password"),
            "api_password": self.BindTextCtrl("opts_api_password"),
        }
        self.BindButton("button_generate", self.OnGenerateClick)
        self.BindButton("button_esphome_copy", self.OnEsphomeCopyClick)

        self.TextEsphome = self.BindTextCtrl("input_esphome")
        self.TextUpk = self.BindTextCtrl("input_upk")
        self.TextStorage = self.BindTextCtrl("input_storage")

        self.EnableFileDrop()

        default_opts = Opts()
        for key, value in Opts.FLAGS.items():
            if key not in self.Opts:
                continue
            self.Opts[key].SetValue(getattr(default_opts, key))
        for key, value in Opts.INPUTS.items():
            if key not in self.Opts:
                continue
            self.Opts[key].ChangeValue(getattr(default_opts, key))

    def GetSettings(self) -> dict:
        return dict(
            opts={key: value.GetValue() for key, value in self.Opts.items()},
            last_dir=self.last_dir,
            last_url=self.last_url,
            disclaimer_shown=self.disclaimer_shown,
        )

    def SetSettings(
        self,
        opts: dict = None,
        last_dir: str = None,
        last_url: str = None,
        disclaimer_shown: bool = None,
        **_,
    ):
        if opts:
            for key, value in opts.items():
                if key in self.Opts:
                    self.Opts[key].SetValue(value)
        if last_dir:
            self.last_dir = last_dir
        if last_url:
            self.last_url = last_url
        if disclaimer_shown is not None:
            self.disclaimer_shown = disclaimer_shown

    def OnUpdate(self, target: wx.Window = None):
        if target is None:
            return
        debug(f"OnUpdate, target: {type(target)}, upk: {self.upk}")
        upk = self.upk
        if not upk:
            self.TextEsphome.Clear()
            self.logs_shown = False
            return
        if target == self.TextUpk:
            self.logs_shown = False

        if not self.disclaimer_shown:
            wx.MessageBox(
                message=DISCLAIMER_TEXT,
                caption="Disclaimer",
                style=wx.ICON_WARNING,
            )
            self.disclaimer_shown = True

        opts = Opts(**self.GetSettings()["opts"])
        yr = generate_yaml(upk, opts)
        self.TextEsphome.ChangeValue(yr.text)

        if not self.logs_shown:
            self.logs_shown = True
            for line in yr.errors:
                error(line)
            if yr.errors:
                wx.MessageBox(
                    message="While generating YAML:\n\n" + "\n".join(yr.errors),
                    caption="Error",
                    style=wx.ICON_ERROR,
                )
            for line in yr.warnings:
                warning(line)
            if yr.warnings:
                wx.MessageBox(
                    message="While generating YAML:\n\n" + "\n".join(yr.warnings),
                    caption="Warning",
                    style=wx.ICON_WARNING,
                )
            for line in yr.logs:
                info(f"UPK: {line}")

    def OnStorageData(self, storage: dict):
        self.storage = storage
        self.DoUpdate()

    def OnStorageError(self, error_text: str):
        self.storage = None
        self.DoUpdate()
        wx.MessageBox(
            message=error_text,
            caption="Error",
            style=wx.ICON_ERROR,
        )

    def OnFileDrop(self, *files):
        if not files:
            return
        file = files[0]
        if not isfile(file):
            return
        self.last_dir = dirname(file)
        work = UpkThread(
            file=file,
            on_storage=self.OnStorageData,
            on_error=self.OnStorageError,
        )
        self.StartWork(work)

    @on_event
    def OnDoKickstartClick(self):
        dialog = wx.TextEntryDialog(
            self,
            message="Enter URL (or IP address) of Kickstart dashboard:",
            caption="Kickstart URL",
            value=self.last_url or "",
        )
        if dialog.ShowModal() != wx.ID_OK:
            dialog.Destroy()
            return
        url = dialog.GetValue().strip()
        dialog.Destroy()
        if not url:
            return

        debug(f"Kickstart URL: {url}")
        self.last_url = url
        work = UpkThread(
            url=url,
            on_storage=self.OnStorageData,
            on_error=self.OnStorageError,
        )
        self.StartWork(work)

    @on_event
    def OnDoCloudcutterClick(self):
        self.DisableAll()
        try:
            devices_slug, devices_name = cloudcutter_list_devices()
            self.EnableAll()
        except Exception as e:
            self.EnableAll()
            LoggingHandler.get().emit_exception(e)
            return

        dialog = wx.SingleChoiceDialog(
            self,
            message="Choose a Cloudcutter profile, that matches your particular device:",
            caption="Cloudcutter profile",
            choices=devices_name,
        )
        if dialog.ShowModal() != wx.ID_OK:
            dialog.Destroy()
            return
        selection = dialog.GetSelection()
        dialog.Destroy()

        self.DisableAll()
        try:
            device = cloudcutter_get_device(devices_slug[selection])
            self.EnableAll()
        except Exception as e:
            self.EnableAll()
            LoggingHandler.get().emit_exception(e)
            return

        self.storage = None
        self.upk = device.get("device_configuration", {})

    @on_event
    def OnDoDumpClick(self):
        title = "Open file"
        flags = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
        init_dir = self.last_dir or os.getcwd()
        with wx.FileDialog(self, title, init_dir, style=flags) as dialog:
            dialog: wx.FileDialog
            if dialog.ShowModal() == wx.ID_CANCEL:
                return
            file = dialog.GetPath()
            self.last_dir = dirname(file)
            work = UpkThread(
                file=file,
                on_storage=self.OnStorageData,
                on_error=self.OnStorageError,
            )
            self.StartWork(work)

    @on_event
    def OnGenerateClick(self):
        self.Notebook.SetSelection(2)

    @on_event
    def OnEsphomeCopyClick(self):
        text = self.TextEsphome.GetValue()
        clip = wx.TextDataObject()
        clip.SetText(text)
        if wx.TheClipboard.Open():
            wx.TheClipboard.SetData(clip)
            wx.TheClipboard.Flush()
            self.TextEsphome.SelectAll()

    @property
    def upk(self):
        text = self.TextUpk.GetValue() or None
        return text and json.loads(text)

    @upk.setter
    def upk(self, value: dict | None):
        text = value and json.dumps(value, indent=4) or ""
        self.TextUpk.ChangeValue(text)
        if value:
            # valid UPK, go to options page
            self.Notebook.SetSelection(1)
        elif value is not None:
            # empty UPK == no UPK in storage
            wx.MessageBox(
                message=(
                    "This device doesn't contain user_param_key config. "
                    "Possible causes:\n"
                    "- it has custom/non-generic firmware\n"
                    "- it uses TuyaMCU\n\n"
                    "Auto-generating ESPHome YAML is not possible."
                ),
                caption="Missing configuration",
                style=wx.ICON_WARNING,
            )
        # else: UPK is None, so we're clearing the state
        self.DoUpdate(self.TextUpk)

    @property
    def storage(self):
        text = self.TextStorage.GetValue() or None
        return text and json.loads(text)

    @storage.setter
    def storage(self, value: dict | None):
        text = value and json.dumps(value, indent=4) or ""
        self.TextStorage.ChangeValue(text)
        if value is None:
            # clear UPK along with storage
            self.upk = None
        else:
            # set UPK if present, or set empty if not
            self.upk = value.get("user_param_key", {})
        self.DoUpdate(self.TextStorage)
