import enum
from typing import Any, Dict
from abc import ABC, abstractmethod

from envipy_additional_information import EnviPyModel

from .dto import BuildResult, EnviPyDTO, EvaluationResult, RunResult


class PropertyType(enum.Enum):
    """
    Enumeration representing different types of properties.

    PropertyType is an Enum class that defines categories or types of properties
    based on their weight or nature. It can typically be used when classifying
    objects or entities by their weight classification, such as lightweight or heavy.
    """

    LIGHTWEIGHT = "lightweight"
    HEAVY = "heavy"


class Plugin(ABC):
    """
    Defines an abstract base class Plugin to serve as a blueprint for plugins.

    This class establishes the structure that all plugin implementations must
    follow. It enforces the presence of required methods to ensure consistent
    functionality across all derived classes.

    """

    @classmethod
    @abstractmethod
    def identifier(cls) -> str:
        pass

    @classmethod
    @abstractmethod
    def name(cls) -> str:
        """
        Represents an abstract method that provides a contract for implementing a method
        to return a name as a string. Must be implemented in subclasses.
        Name must be unique across all plugins.

        Methods
        -------
        name() -> str
            Abstract method to be defined in subclasses, which returns a string
            representing a name.
        """
        pass

    @classmethod
    @abstractmethod
    def display(cls) -> str:
        """
        An abstract method that must be implemented by subclasses to display
        specific information or behavior. The method ensures that all subclasses
        provide their own implementation of the display functionality.

        Raises:
            NotImplementedError: Raises this error when the method is not implemented
            in a subclass.

        Returns:
            str: A string used in dropdown menus or other user interfaces to display
        """
        pass


class Property(Plugin):
    def parameters(self) -> Dict[str, Any]:
        """
        Returns the parameters of the PropertyPlugin.
        """
        return {}

    @classmethod
    @abstractmethod
    def requires_rule_packages(cls) -> bool:
        """
        Defines an abstract method to determine whether rule packages are required.

        This method should be implemented by subclasses to specify if they depend
        on rule packages for their functioning.

        Raises:
            NotImplementedError: If the subclass has not implemented this method.

        @return: A boolean indicating if rule packages are required.
        """
        pass

    @classmethod
    @abstractmethod
    def requires_data_packages(cls) -> bool:
        """
        Defines an abstract method to determine whether data packages are required.

        This method should be implemented by subclasses to specify if they depend
        on data packages for their functioning.

        Raises:
            NotImplementedError: If the subclass has not implemented this method.

        Returns:
            bool: True if the service requires data packages, False otherwise.
        """
        pass

    @abstractmethod
    def get_type(self) -> PropertyType:
        """
        An abstract method that provides the type of property. This method must
        be implemented by subclasses to specify the appropriate property type.

        Raises:
            NotImplementedError: If the method is not implemented by a subclass.

        Returns:
            PropertyType: The type of the property associated with the implementation.
        """
        pass

    def is_heavy(self):
        """
        Determines if the current property type is heavy.

        This method evaluates whether the property type returned from the `get_type()`
        method is classified as `HEAVY`. It utilizes the `PropertyType.HEAVY` constant
        for this comparison.

        Raises:
            AttributeError: If the `get_type()` method is not defined or does not return
            a valid value.

        Returns:
            bool: True if the property type is `HEAVY`, otherwise False.
        """
        return self.get_type() == PropertyType.HEAVY

    @abstractmethod
    def build(self, eP: EnviPyDTO, *args, **kwargs) -> BuildResult | None:
        """
        Abstract method to prepare and construct a specific build process based on the provided
        environment data transfer object (EnviPyDTO). This method should be implemented by
        subclasses to handle the particular requirements of the environment.

        Parameters:
        eP : EnviPyDTO
            The data transfer object containing environment details for the build process.

        *args :
            Additional positional arguments required for the build.

        **kwargs :
            Additional keyword arguments to offer flexibility and customization for
            the build process.

        Returns:
        BuildResult | None
            Returns a BuildResult instance if the build operation succeeds, else returns None.

        Raises:
        NotImplementedError
            If the method is not implemented in a subclass.
        """
        pass

    @abstractmethod
    def run(self, eP: EnviPyDTO, *args, **kwargs) -> RunResult:
        """
        Represents an abstract base class for executing a generic process with
        provided parameters and returning a standardized result.

        Attributes:
            None.

        Methods:
            run(eP, *args, **kwargs):
                Executes a task with specified input parameters and optional
                arguments, returning the outcome in the form of a RunResult object.
                This is an abstract method and must be implemented in subclasses.

        Raises:
            NotImplementedError: If the subclass does not implement the abstract
            method.

        Parameters:
            eP (EnviPyDTO): The primary object containing information or data required
            for processing. Mandatory.
            *args: Variable length argument list for additional positional arguments.
            **kwargs: Arbitrary keyword arguments for additional options or settings.

        Returns:
            RunResult: A result object encapsulating the status, output, or details
            of the process execution.

        """
        pass

    @abstractmethod
    def evaluate(self, eP: EnviPyDTO, *args, **kwargs) -> EvaluationResult:
        """
        Abstract method for evaluating data based on the given input and additional arguments.

        This method is intended to be implemented by subclasses and provides
        a mechanism to perform an evaluation procedure based on input encapsulated
        in an EnviPyDTO object.

        Parameters:
        eP : EnviPyDTO
            The data transfer object containing necessary input for evaluation.
        *args : tuple
            Additional positional arguments for the evaluation process.
        **kwargs : dict
            Additional keyword arguments for the evaluation process.

        Returns:
        EvaluationResult
            The result of the evaluation performed by the method.

        Raises:
        NotImplementedError
            If the method is not implemented in the subclass.
        """
        pass

    @abstractmethod
    def build_and_evaluate(self, eP: EnviPyDTO, *args, **kwargs) -> EvaluationResult:
        """
        An abstract method designed to build and evaluate a model or system using the provided
        environmental parameters and additional optional arguments.

        Args:
            eP (EnviPyDTO): The environmental parameters required for building and evaluating.
            *args: Additional positional arguments.
            **kwargs: Additional keyword arguments.

        Returns:
            EvaluationResult: The result of the evaluation process.

        Raises:
            NotImplementedError: If the method is not implemented by a subclass.
        """
        pass


class Classifier(Plugin):
    Config: type[EnviPyModel] | None = None

    def __init__(self, config: EnviPyModel | None = None):
        self.config = config

    @classmethod
    def has_config(cls) -> bool:
        return cls.Config is not None

    @classmethod
    def parse_config(cls, data: dict | None = None) -> EnviPyModel | None:
        if cls.Config is None:
            return None

        # remove empty strings a.k.a unset params to not overwrite defaults
        cpy = {}
        if data is not None:
            for k, v in data.items():
                if v != "":
                    cpy[k] = v
        return cls.Config(**cpy)

    @classmethod
    def create(cls, data: dict | None = None):
        return cls(cls.parse_config(data))

    @classmethod
    @abstractmethod
    def requires_rule_packages(cls) -> bool:
        """
        Defines an abstract method to determine whether rule packages are required.

        This method should be implemented by subclasses to specify if they depend
        on rule packages for their functioning.

        Raises:
            NotImplementedError: If the subclass has not implemented this method.

        @return: A boolean indicating if rule packages are required.
        """
        pass

    @classmethod
    @abstractmethod
    def requires_data_packages(cls) -> bool:
        """
        Defines an abstract method to determine whether data packages are required.

        This method should be implemented by subclasses to specify if they depend
        on data packages for their functioning.

        Raises:
            NotImplementedError: If the subclass has not implemented this method.

        Returns:
            bool: True if the service requires data packages, False otherwise.
        """
        pass

    def parameters(self) -> Dict[str, Any]:
        """
        Returns the parameters of the ClassifierPlugin.
        """
        return {}

    @abstractmethod
    def build(self, eP: EnviPyDTO, *args, **kwargs) -> BuildResult | None:
        """
        Abstract method to prepare and construct a specific build process based on the provided
        environment data transfer object (EnviPyDTO). This method should be implemented by
        subclasses to handle the particular requirements of the environment.

        Parameters:
        eP : EnviPyDTO
            The data transfer object containing environment details for the build process.

        *args :
            Additional positional arguments required for the build.

        **kwargs :
            Additional keyword arguments to offer flexibility and customization for
            the build process.

        Returns:
        BuildResult | None
            Returns a BuildResult instance if the build operation succeeds, else returns None.

        Raises:
        NotImplementedError
            If the method is not implemented in a subclass.
        """
        pass

    @abstractmethod
    def run(self, eP: EnviPyDTO, *args, **kwargs) -> RunResult:
        """
        Represents an abstract base class for executing a generic process with
        provided parameters and returning a standardized result.

        Attributes:
            None.

        Methods:
            run(eP, *args, **kwargs):
                Executes a task with specified input parameters and optional
                arguments, returning the outcome in the form of a RunResult object.
                This is an abstract method and must be implemented in subclasses.

        Raises:
            NotImplementedError: If the subclass does not implement the abstract
            method.

        Parameters:
            eP (EnviPyDTO): The primary object containing information or data required
            for processing. Mandatory.
            *args: Variable length argument list for additional positional arguments.
            **kwargs: Arbitrary keyword arguments for additional options or settings.

        Returns:
            RunResult: A result object encapsulating the status, output, or details
            of the process execution.

        """
        pass

    @abstractmethod
    def evaluate(self, eP: EnviPyDTO, *args, **kwargs) -> EvaluationResult | None:
        """
        Abstract method for evaluating data based on the given input and additional arguments.

        This method is intended to be implemented by subclasses and provides
        a mechanism to perform an evaluation procedure based on input encapsulated
        in an EnviPyDTO object.

        Parameters:
        eP : EnviPyDTO
            The data transfer object containing necessary input for evaluation.
        *args : tuple
            Additional positional arguments for the evaluation process.
        **kwargs : dict
            Additional keyword arguments for the evaluation process.

        Returns:
        EvaluationResult
            The result of the evaluation performed by the method.

        Raises:
        NotImplementedError
            If the method is not implemented in the subclass.
        """
        pass

    @abstractmethod
    def build_and_evaluate(self, eP: EnviPyDTO, *args, **kwargs) -> EvaluationResult | None:
        """
        An abstract method designed to build and evaluate a model or system using the provided
        environmental parameters and additional optional arguments.

        Args:
            eP (EnviPyDTO): The environmental parameters required for building and evaluating.
            *args: Additional positional arguments.
            **kwargs: Additional keyword arguments.

        Returns:
            EvaluationResult: The result of the evaluation process.

        Raises:
            NotImplementedError: If the method is not implemented by a subclass.
        """
        pass
