from yta_editor_parameters.specifications import ParameterSpecification
from yta_validation import PythonValidator
from typing import Union


class ParameterSpecificationValidator:
    """
    Class to validate if a parameter value is
    accepted according to the specification
    provided.
    """

    @staticmethod
    def validate_parameter(
        key: str,
        value: any,
        specification: ParameterSpecification
    ) -> Union[any, None]:
        """
        Validate that the `key` provided is associated to
        the `specification` and that the `value` given is
        valid according to that same specification.

        This method will return the value of the parameter,
        that could be the default one if existing and the
        `value` provided was `None`.
        """
        if key != specification.name:
            raise Exception('The "key" provided is not associated to the specification given.')
        
        if (
            value is None and
            specification.is_required and
            specification.default is None
        ):
            raise Exception(f'The "value" of the "{key}" parameter provided is `None` and the specification is required and does not have any default value.')
        
        if (
            value is None and
            specification.default is not None
        ):
            value = specification.default

        value = (
            specification.default
            if value is None else
            value
        )

        if value is not None:
            """
            Transforms ints to float, and viceversa, if needed
            """
            if specification.type in [float, int]:
                try:
                    value = specification.type(value)
                except:
                    raise Exception(f"The '{specification.name}' parameter provided must be '{specification.type}' and it is not casteable as this type.")

            if not PythonValidator.is_instance_of(value, specification.type):
                raise Exception(f"The '{specification.name}' parameter value must be '{specification.type}'")

            if PythonValidator.is_orderable(value):
                if (
                    specification.min is not None and
                    value < specification.min
                ):
                    raise Exception(f"'The '{specification.name}' parameter value is below the min={specification.min}")

                if (
                    specification.max is not None and
                    value > specification.max
                ):
                    raise Exception(f"'The '{specification.name}' parameter value is above the max={specification.max}")
        
        return value


    @staticmethod
    def validate_parameters(
        parameters: dict[str, any],
        parameter_specifications: list[ParameterSpecification]
    ) -> Union[dict[str, any], None]:
        """
        Validate that the `parameters` provided are accepted
        and valid for the `parameter_specifications` given,
        which means that the required values are provided and
        the values have the accepted type and valid values.

        This method will return the `parameters` dict that
        could have been modified by replacing `None` values
        for the ones specified as default values.
        """

        tmp_parameters = {}
        for parameter_specification in parameter_specifications:
            key = parameter_specification.name
            value = (
                parameters[key]
                if key in parameters else
                None
            )

            tmp_parameters[key] = ParameterSpecificationValidator.validate_parameter(
                key = key,
                value = value,
                specification = parameter_specification
            )
            
        """
        Using this new dict we will only preserve those
        parameters that are registered as expected, avoiding
        errors with unexpected parameters reaching the nodes.
        """
        return tmp_parameters