import inspect
from enum import Enum
from typing import Any, Optional

from pydase import DataService
from pydase.components import NumberSlider
from pydase.units import Quantity
from pydase.utils.helpers import get_object_attr_from_path
from pydase.version import __version__


class RPCInterface(object):
    """RPC interface to be passed to tiqi_rpc.Server to interface with Ionizer."""

    def __init__(
        self, service: DataService, info: dict[str, Any] = {}, *args: Any, **kwargs: Any
    ) -> None:
        self._service = service
        self._info = info

    async def version(self) -> str:
        return f"pydase v{__version__}"

    async def name(self) -> str:
        return self._service.__class__.__name__

    async def info(self) -> dict:
        return self._info

    async def get_props(self, name: Optional[str] = None) -> dict[str, Any]:
        if name is None:
            return self._service.serialize()
        return self._service.serialize()

    async def get_param(self, full_access_path: str) -> Any:
        """Returns the value of the parameter given by the full_access_path.

        This method is called when Ionizer initilizes the Plugin or refreshes. The
        widgets need to store the full_access_path in their name attribute.
        """
        param = get_object_attr_from_path(self._service, full_access_path.split("."))
        if isinstance(param, NumberSlider):
            return param.value
        elif isinstance(param, DataService):
            return param.serialize()
        elif inspect.ismethod(param):
            # explicitly serialize any methods that will be returned
            full_access_path = param.__name__
            args = inspect.signature(param).parameters
            return f"{full_access_path}({', '.join(args)})"
        elif isinstance(param, Enum):
            return param.value
        elif isinstance(param, Quantity):
            return param.m
        else:
            return param

    async def set_param(self, full_access_path: str, value: Any) -> None:
        parent_path_list = full_access_path.split(".")[:-1]
        parent_object = get_object_attr_from_path(self._service, parent_path_list)
        attr_name = full_access_path.split(".")[-1]
        # I don't want to trigger the execution of a property getter as this might take
        # a while when connecting to remote devices
        if not isinstance(
            getattr(type(parent_object), attr_name, None),
            property,
        ):
            current_value = getattr(parent_object, attr_name, None)
            if isinstance(current_value, Enum) and isinstance(value, int):
                # Ionizer sets the enums using the position of the definition order
                # This works as definition order is kept, see e.g.
                # https://docs.python.org/3/library/enum.html#enum.EnumType.__iter__
                # I need to use the name attribute as this is what
                # DataService.__set_attribute_based_on_type expects
                value = list(current_value.__class__)[value].name
            elif isinstance(current_value, NumberSlider):
                parent_path_list.append(attr_name)
                attr_name = "value"
        self._service.update_DataService_attribute(parent_path_list, attr_name, value)

    async def remote_call(self, full_access_path: str, *args: Any) -> Any:
        full_access_path_list = full_access_path.split(".")
        method_object = get_object_attr_from_path(self._service, full_access_path_list)
        return method_object(*args)

    async def emit(self, message: str) -> None:
        self.notify(message)

    def notify(self, message: str) -> None:
        """
        This method will be overwritten by the tiqi-rpc server.

        Args:
            message (str): Notification message.
        """
        return