"""
A list of functions that are used as values for validators attribute of a type.
"""

from django.core.exceptions import ValidationError
from pprint import pformat


def not_empty(value: str):
    if not value:
        raise ValidationError("Value cannot be empty")


class PreviewValidator:
    """
    return instance of the class from the validator to show the result of validation
    """

    def __init__(self, preview_input: str, preview_output: str):
        self.preview_input = preview_input
        self.preview_output = preview_output


class PreviewValidationError(ValidationError):
    """
    use class instead of ValidatorError to show the input arguments that cause the ValidationError
    """

    def __init__(self, preview_input: str, *args, **kwargs) -> None:
        self.preview_input = preview_input
        super().__init__(*args, **kwargs)


class call_validator:
    """
    create a valiator that calls the function with the given args and kwargs.
    """

    has_call_representation = True

    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

    def __call__(self, func):
        try:
            return func(*self.args, **self.kwargs)
        except Exception as e:
            raise ValidationError(str(e))

    def __str__(self) -> str:
        ret = ""
        if self.args:
            ret += ", ".join([pformat(arg) for arg in self.args])

        if self.kwargs:
            if ret:
                ret += ", "
            ret += ", ".join(
                [f"{key}={pformat(value)}" for key, value in self.kwargs.items()]
            )

        return ret

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} {str(self)}>"


class result_validator(call_validator):
    """
    Not only call the function, but also validate the result of the call.

    takes two new arguments:

    * function that validates the result of the call
    * error message that will be shown if the function returns False
    """

    def __init__(self, result_validator, error_message, *args, **kwargs):
        self.result_validator = result_validator
        self.error_message = error_message
        super().__init__(*args, **kwargs)

    def __call__(self, func):
        ret = super().__call__(func)
        if not self.result_validator(ret):
            raise ValidationError(self.error_message)
        return ret


class gen_call_validator(call_validator):
    """
    Same as call_validator, but the args and kwargs are generated by a given function.

    Create a valiator that calls the function that generates the args and kwargs, that will be used for the call.

    The function will be regenerated every time when the validator is called.

    The reason of having one is when you are not able to get possible args at the time of the creation of the validator.
    """

    def __init__(self, gen_args_kwargs_func):
        self.gen_args_kwargs_func = gen_args_kwargs_func
        self._args_kwargs = None

    def gen_args_kwargs(self):
        return self.gen_args_kwargs_func()

    @property
    def args_kwargs(self):
        if self._args_kwargs is None:
            self._args_kwargs = self.gen_args_kwargs()

            assert (
                len(self._args_kwargs) == 2
            ), "gen_args_kwargs_func must return a tuple of args and kwargs"
            assert isinstance(
                self._args_kwargs[0], (list, tuple)
            ), "args must be a list or tuple"
            assert isinstance(self._args_kwargs[1], dict), "kwargs must be a dict"
        return self._args_kwargs

    @property
    def args(self):
        return self.args_kwargs[0]

    @property
    def kwargs(self):
        return self.args_kwargs[1]

    def __call__(self, func):
        try:
            return super().__call__(func)
        finally:
            self._args_kwargs = None


class gen_args_call_validator(gen_call_validator):
    """
    Same as gen_call_validator, but only generates the list for args.
    """

    def gen_args_kwargs(self):
        return self.gen_args_kwargs_func(), {}


class gen_signle_arg_call_validator(gen_call_validator):
    """
    Same as gen_call_validator, but only generates one arg.
    """

    def gen_args_kwargs(self):
        return [self.gen_args_kwargs_func()], {}


class gen_kwargs_call_validator(gen_call_validator):
    """
    Same as gen_call_validator, but only generates the dict for kwargs.
    """

    def gen_args_kwargs(self):
        return [], self.gen_args_kwargs_func()
