from __future__ import annotations

from typing import TYPE_CHECKING

from sila2.framework.abc.sila_error import SilaError
from sila2.framework.defined_execution_error_node import DefinedExecutionErrorNode
from sila2.framework.fully_qualified_identifier import FullyQualifiedIdentifier

if TYPE_CHECKING:
    from sila2.client.sila_client import SilaClient
    from sila2.pb2_stubs.SiLAFramework_pb2 import DefinedExecutionError as SilaDefinedExecutionError
    from sila2.pb2_stubs.SiLAFramework_pb2 import SiLAError


class DefinedExecutionError(SilaError):
    """
    Issued by a SiLA Server during command execution, property access, or related to SiLA Client Metadata,
    that is not a :py:class:`sila2.framework.DefinedExecutionError` (i.e. not specified in the feature definition).

    Notes
    -----
    The code generator generates a DefinedExecutionError subclass for each error defined in a feature.

    - Server Development: Never use this class directly. Always use the generated error classes.
    - Client usage: Client classes generated by the code generator will raise received defined execution errors as
        instance of the generated error classes. Dynamic clients (plain :py:class:`sila2.client.SilaClient` objects)
        raise instances of this class directly.
    """

    identifier: str
    """Error identifier, from the feature definition"""
    message: str
    """Error message"""
    fully_qualified_identifier: FullyQualifiedIdentifier
    """Fully qualified error identifier"""

    def __init__(self, error_node: DefinedExecutionErrorNode, message: str):
        self.message = message
        self.identifier = error_node._identifier
        self.fully_qualified_identifier = error_node.fully_qualified_identifier

        # if self is a specific subclass
        if self.__class__.__name__ == error_node._identifier:
            super().__init__(message)
        else:
            super().__init__(f"{error_node._identifier}: {message}")

    def to_message(self) -> SiLAError:
        return self._pb2_module.SiLAError(
            definedExecutionError=self._pb2_module.DefinedExecutionError(
                errorIdentifier=self.fully_qualified_identifier, message=self.message
            )
        )

    @classmethod
    def from_message(cls, message: SilaDefinedExecutionError, client: SilaClient) -> DefinedExecutionError:
        return cls(
            client._children_by_fully_qualified_identifier[FullyQualifiedIdentifier(message.errorIdentifier)],
            message.message,
        )
