"""StateMachine."""

import logging
from typing import Optional

from pubnub.event_engine.dispatcher import Dispatcher
from pubnub.event_engine.models import effects, events, states


class StateMachine:
    _current_state: states.PNState
    _context: states.PNContext
    _effect_list: list[effects.PNEffect]
    _enabled: bool

    def __init__(
        self,
        initial_state: states.PNState,
        dispatcher_class: Optional[Dispatcher] = None,
    ) -> None:
        self._context = states.PNContext()
        self._current_state = initial_state(self._context)
        self._listeners = {}
        self._effect_list = []
        if dispatcher_class is None:
            dispatcher_class = Dispatcher
        self._dispatcher = dispatcher_class(self)
        self._enabled = True

    def get_state_name(self):
        return self._current_state.__class__.__name__

    def get_context(self) -> states.PNContext:
        return self._current_state._context

    def get_dispatcher(self) -> Dispatcher:
        return self._dispatcher

    def trigger(self, event: events.PNEvent) -> states.PNTransition:
        logging.debug(
            f"Triggered {event.__class__.__name__}({event.__dict__}) on {self.get_state_name()}"
        )
        if not self._enabled:
            logging.error("EventEngine is not enabled")
            return False
        if event.get_name() in self._current_state._transitions:
            self._effect_list.clear()
            effect = self._current_state.on_exit()
            logging.debug(f"On exit effect: {effect.__class__.__name__}")

            if effect:
                self._effect_list.append(effect)

            transition: states.PNTransition = self._current_state.on(
                event, self._context
            )

            self._current_state = transition.state(self._current_state.get_context())

            self._context = transition.context
            if transition.effect:
                if isinstance(transition.effect, list):
                    logging.debug("unpacking list")
                    for effect in transition.effect:
                        logging.debug(f"Transition effect: {effect.__class__.__name__}")
                        self._effect_list.append(effect)
                else:
                    logging.debug(
                        f"Transition effect: {transition.effect.__class__.__name__}"
                    )
                    self._effect_list.append(transition.effect)

            effect = self._current_state.on_enter(self._context)
            if effect:
                logging.debug(f"On enter effect: {effect.__class__.__name__}")
                self._effect_list.append(effect)

        else:
            # we're ignoring events unhandled
            logging.debug(
                f"unhandled event?? {event.__class__.__name__} in {self._current_state.__class__.__name__}"
            )
            self.stop()

        self.dispatch_effects()

    def dispatch_effects(self):
        for effect in self._effect_list:
            logging.debug(f"dispatching {effect.__class__.__name__} {id(effect)}")
            self._dispatcher.dispatch_effect(effect)

        self._effect_list.clear()

    def stop(self):
        self._enabled = False


if __name__ == "__main__":
    machine = StateMachine(states.UnsubscribedState)
    logging.debug(f"machine initialized. Current state: {machine.get_state_name()}")
    effect = machine.trigger(
        events.SubscriptionChangedEvent(channels=["fail"], groups=[])
    )

    machine.add_listener(
        effects.PNEffect,
        lambda x: logging.debug(f"Catch All Logger: {effect.__dict__}"),
    )

    machine.add_listener(
        effects.EmitMessagesEffect,
    )
    effect = machine.trigger(events.DisconnectEvent())
    logging.debug(
        f"SubscriptionChangedEvent triggered with channels=[`fail`].  Curr state: {machine.get_state_name()}"
    )
    logging.debug(f"effect queue: {machine._effect_list}")
