from typing import List, Any, Optional

import wx
from clarabot_nano_common.ui.utility import Slots
from clarabot_nano_common.utility.serialize import msgpack_dumps

from clarabot_nano.common.core.message import MSG_TYPE_REQUEST_CONFIG_UPDATE
from clarabot_nano.control.client import WxCoreInterface
from clarabot_nano.control.common import gateway_command_result
from clarabot_nano.control.icon import get_wx_nano_icon


class AbstractNanoRequestProgressStep(object):
    def text(self) -> str:
        raise NotImplementedError()

    def request(self, core_interface: WxCoreInterface) -> Optional[bytes]:
        """
        Return the core-request's id for tracking. Return None to indicate moving to the next step without response.
        """
        raise NotImplementedError()

    def response(self, successful: bool, core: bool, result: Any) -> Optional[bool]:
        """
        Return True if the response is successful and the progress shall proceed to the next step.
        Return None to ignore the response.
        """
        return successful


class CoreNanoRequestProgressStep(AbstractNanoRequestProgressStep):
    def __init__(self, text: str, request: List[bytes]):
        self._t = text
        self._r = request

    def text(self) -> str:
        return self._t

    def request(self, core_interface: WxCoreInterface) -> bytes:
        return core_interface.core_request(self._r, track_reply=True)


class ConfigUpdateNanoRequestProgressStep(CoreNanoRequestProgressStep):
    def __init__(self, text: str, handler_key: str, **kwargs):
        kwargs["_handler_key"] = handler_key
        CoreNanoRequestProgressStep.__init__(self, text, [MSG_TYPE_REQUEST_CONFIG_UPDATE, msgpack_dumps(kwargs)])


class GatewayNanoRequestProgressStep(AbstractNanoRequestProgressStep):
    def __init__(self, text: str, request: List[bytes], gateway_response: str):
        self._t = text
        self._r = request
        self._g = gateway_response

    def text(self) -> str:
        return self._t

    def request(self, core_interface: WxCoreInterface) -> bytes:
        return core_interface.gateway_request(self._r)

    def response(self, successful: bool, core: bool, result: Any) -> Optional[bool]:
        if core:
            return
        if successful and result == self._g:
            return True
        return False


class NanoRequestProgressDialog(wx.GenericProgressDialog, Slots):
    def __init__(
        self,
        core_interface: WxCoreInterface,
        parent,
        title: str,
        steps: List[AbstractNanoRequestProgressStep],
        done_text: str,
    ):
        # NOTE: wx has some issue on darwin that prevents the dialog from closing when the progress bar's value
        # becomes the maximum. This maybe the result of having this dialog in a modal execution, which is a little
        # unusual, although not unsupported. As a work-around we won't reach maximum and call EndModal ourselves
        # instead of using the AUTO_HIDE feature.
        wx.GenericProgressDialog.__init__(self, title, "", maximum=len(steps) + 1, parent=parent, style=wx.PD_APP_MODAL)
        Slots.__init__(self)
        self.SetIcon(get_wx_nano_icon())
        self.core_interface = core_interface
        self._steps = steps
        self._done_text = done_text
        self._step = 0
        self._core_request_id = None

        # Re-enable parenting on this to handle messages as we want.
        self.SetExtraStyle(self.GetExtraStyle() & ~wx.WS_EX_TRANSIENT)

        self._step_timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self._on_step_timer, self._step_timer)
        self.bind_signal(core_interface.gateway_command_result, self._on_gateway_command_result)
        self.bind_signal(core_interface.core_reply_signal, self._on_core_reply)

    def ShowModal(self):
        self._next_step()  # first step
        return wx.GenericProgressDialog.ShowModal(self)

    def _on_step_timer(self, *args):
        self._step += 1
        self._next_step()

    def _next_step(self):
        if self._step == len(self._steps):
            with wx.GenericMessageDialog(self, self._done_text, gettext("Done"), wx.ICON_INFORMATION | wx.OK) as d:
                d.ShowModal()
            self.Update(len(self._steps))
            self.EndModal(0)
        else:
            step = self._steps[self._step]
            self.Update(self._step, step.text())
            self._core_request_id = step.request(self.core_interface)
            if not self._core_request_id:
                self._step_timer.StartOnce(5)

    def _on_core_reply(self, request_id, reply):
        if self._core_request_id == request_id:
            successful = isinstance(reply, dict) and reply.get("ok")
            ok = self._steps[self._step].response(successful, True, reply)
            if ok is None:
                pass
            elif ok:
                self._step += 1
                self._next_step()
            else:
                self._on_error(gettext("Failed to request core."))

    def _on_gateway_command_result(self, successful, result):
        ok = self._steps[self._step].response(successful, False, result)
        if ok is None:
            pass
        elif ok:
            self._step += 1
            self._next_step()
        else:
            self._on_error(gateway_command_result(result, not successful))

    def _on_error(self, text: str):
        with wx.GenericMessageDialog(self, text, gettext("Error"), wx.ICON_ERROR | wx.OK) as d:
            d.ShowModal()
        self.Update(len(self._steps))
        self.EndModal(0)
