from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any


class ValidationError(Exception):
    """Raised when a validator rejects input."""

    def __init__(self, message: str, field: str | None = None) -> None:
        super().__init__(message)
        self.message = message
        self.field = field

    def __str__(self) -> str:
        if self.field:
            return f"[{self.field}] {self.message}"
        return self.message


class BaseValidator(ABC):
    """
    Abstract async validator.

    Subclass and implement :meth:`validate`. The validator may return a
    (possibly transformed) value or raise :exc:`ValidationError`::

        class PositiveIntValidator(BaseValidator):
            async def validate(self, data: int) -> int:
                if data <= 0:
                    raise ValidationError("must be positive", field="value")
                return data
    """

    @abstractmethod
    async def validate(self, data: Any) -> Any:
        """Validate (and optionally transform) data.

        Args:
            data: The value to validate.

        Returns:
            The (possibly transformed) value.

        Raises:
            ValidationError: When validation fails.
        """
        ...

    async def __call__(self, data: Any) -> Any:
        return await self.validate(data)


class ValidatorPipeline:
    """
    Chain multiple validators, passing the output of each as input to the next.

    If any validator raises :exc:`ValidationError` the pipeline stops
    immediately and re-raises::

        pipeline = ValidatorPipeline(TrimWhitespace(), MaxLength(255))
        clean_title = await pipeline.validate(raw_title)
    """

    def __init__(self, *validators: BaseValidator) -> None:
        self._validators = validators

    async def validate(self, data: Any) -> Any:
        for validator in self._validators:
            data = await validator(data)
        return data

    async def __call__(self, data: Any) -> Any:
        return await self.validate(data)


class RequiredValidator(BaseValidator):
    """Reject None and empty-string values."""

    def __init__(self, field: str | None = None) -> None:
        self._field = field

    async def validate(self, data: Any) -> Any:
        if data is None or data == "":
            raise ValidationError("This field is required.", field=self._field)
        return data


class MaxLengthValidator(BaseValidator):
    """Reject strings longer than max_length."""

    def __init__(self, max_length: int, field: str | None = None) -> None:
        self._max = max_length
        self._field = field

    async def validate(self, data: Any) -> Any:
        if isinstance(data, str) and len(data) > self._max:
            raise ValidationError(
                f"Must be at most {self._max} characters.", field=self._field
            )
        return data
