from __future__ import annotations

from typing import TYPE_CHECKING, Any

from anytree import LevelGroupOrderIter, NodeMixin, RenderTree, findall
from munch import Munch

if TYPE_CHECKING:
    from ekfsm.devices.generic import Device

    from .slots import Slot


class SysTree(NodeMixin):
    """
    Base class for all system components including Hardware Modules and Devices.
    """

    def __init__(self, name: str, abort: bool = False) -> None:
        from ekfsm.log import ekfsm_logger

        self.logger = ekfsm_logger(name)
        self.name = name
        self.abort = abort

    def _render_tree(self) -> str:
        output = ""
        for pre, _, node in RenderTree(self):
            output += f"{pre}{repr(node)}" + "\n"
        return output

    def __str__(self):
        return self._render_tree()

    def print(self) -> None:
        print(self)


class HwModule(SysTree):
    """
    A HwModule represents an instantiation of a specifc hw board type,
    for example an instance of an EKF SC9 board.
    """

    def __init__(
        self,
        instance_name: str,
        config: dict,
        slot: Slot,
        abort: bool = False,
        *args,
        **kwargs,
    ) -> None:
        from ekfsm.core.utils import deserialize_hardware_tree

        from .slots import SlotType

        super().__init__(instance_name, abort=abort)
        self._slot: Slot = slot
        self.config = config

        self.id, self.board_type, slot_type, self.children = deserialize_hardware_tree(
            self.logger, self.config, parent=self
        )

        self.slot_type = SlotType.from_string(slot_type)

        for children in LevelGroupOrderIter(self):
            for child in children:
                for sub_child in child.children:
                    setattr(child, sub_child.name.lower(), sub_child)

                    # If the device provides functions, add function and corresponding attributes to this hw module
                    self._create_functions_from_node_providers(sub_child)

    @property
    def is_master(self) -> bool:
        slot_attributes = self.slot.attributes
        if slot_attributes is not None and hasattr(slot_attributes, "is_master"):
            return slot_attributes.is_master
        return False

    def _create_functions_from_node_providers(self, device: Device) -> None:
        providers: Munch | None = getattr(device, "provides", None)

        if providers is not None:
            for key, value in providers.items():
                if key not in self.__dict__:
                    provider = Munch()
                else:
                    provider = getattr(self, key)

                provider.update(value)

                setattr(self, key, provider)

    def probe(self, *args, **kwargs) -> bool:
        from ekfsm.core.probe import ProbeableDevice

        nodes = findall(self, lambda node: isinstance(node, ProbeableDevice))
        for node in nodes:
            try:
                if node.probe(*args, **kwargs):
                    return True
            except Exception as e:
                self.logger.error(f"Error probing {node}: {e}")

        return False

    def info(self) -> dict[str, Any]:
        """
        Returns a dictionary with information about the hardware module.
        """
        return {
            "name": self.instance_name,
            "slot": self.slot,
        }

    @property
    def instance_name(self) -> str:
        if self.name is None:
            raise RuntimeError("instance name not set")
        return self.name

    @property
    def slot(self) -> Slot:
        if self._slot is None:
            raise RuntimeError("slot not set")
        return self._slot

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(name={self.name}, type={self.board_type})"
