"""
Validator classes for YAML-defined field validation rules.

This module provides validator classes that can be configured via YAML
and injected into AtomSQL field validation.
"""

import re
import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Pattern, Union

logger = logging.getLogger(__name__)


class ValidationError(Exception):
    """Raised when field validation fails."""

    def __init__(self, message: str, field_name: str = None, value: Any = None):
        self.message = message
        self.field_name = field_name
        self.value = value
        super().__init__(self.format_message())

    def format_message(self) -> str:
        if self.field_name:
            return f"Validation failed for field '{self.field_name}': {self.message}"
        return f"Validation failed: {self.message}"


class Validator(ABC):
    """Base class for all validators."""

    def __init__(self, message: Optional[str] = None):
        """
        Initialize the validator.

        Args:
            message: Custom error message. If not provided, a default message is used.
        """
        self.custom_message = message

    @abstractmethod
    def __call__(self, value: Any, field_name: str = None) -> None:
        """
        Validate a value.

        Args:
            value: The value to validate.
            field_name: The name of the field being validated (for error messages).

        Raises:
            ValidationError: If validation fails.
        """
        pass

    def get_error_message(self, default_message: str) -> str:
        """Return custom message if set, otherwise the default."""
        return self.custom_message or default_message


class RegexValidator(Validator):
    """
    Validates that a value matches a regular expression pattern.

    YAML Example:
        validation:
          - type: regex
            pattern: "^[A-Z]{2}[0-9]{6}$"
            message: "Must be 2 uppercase letters followed by 6 digits"
    """

    def __init__(
        self,
        pattern: Union[str, Pattern],
        flags: int = 0,
        message: Optional[str] = None,
    ):
        """
        Initialize the regex validator.

        Args:
            pattern: The regex pattern (string or compiled pattern).
            flags: Regex flags (e.g., re.IGNORECASE).
            message: Custom error message.
        """
        super().__init__(message)
        if isinstance(pattern, str):
            self.pattern = re.compile(pattern, flags)
        else:
            self.pattern = pattern
        self.pattern_str = pattern if isinstance(pattern, str) else pattern.pattern

    def __call__(self, value: Any, field_name: str = None) -> None:
        if value is None:
            return  # Let nullable validation handle None values

        str_value = str(value)
        if not self.pattern.match(str_value):
            message = self.get_error_message(
                f"Value '{str_value}' does not match pattern '{self.pattern_str}'"
            )
            raise ValidationError(message, field_name, value)


class RangeValidator(Validator):
    """
    Validates that a numeric value is within a specified range.

    YAML Example:
        validation:
          - type: range
            min: 0
            max: 100
            message: "Value must be between 0 and 100"
    """

    def __init__(
        self,
        min_value: Optional[Union[int, float, Decimal]] = None,
        max_value: Optional[Union[int, float, Decimal]] = None,
        message: Optional[str] = None,
    ):
        """
        Initialize the range validator.

        Args:
            min_value: Minimum allowed value (inclusive).
            max_value: Maximum allowed value (inclusive).
            message: Custom error message.
        """
        super().__init__(message)
        if min_value is None and max_value is None:
            raise ValueError("At least one of min_value or max_value must be provided")
        self.min_value = min_value
        self.max_value = max_value

    def __call__(self, value: Any, field_name: str = None) -> None:
        if value is None:
            return

        try:
            numeric_value = float(value)
        except (TypeError, ValueError):
            message = self.get_error_message(
                f"Value '{value}' is not a valid number for range validation"
            )
            raise ValidationError(message, field_name, value)

        if self.min_value is not None and numeric_value < self.min_value:
            message = self.get_error_message(
                f"Value {value} is less than minimum {self.min_value}"
            )
            raise ValidationError(message, field_name, value)

        if self.max_value is not None and numeric_value > self.max_value:
            message = self.get_error_message(
                f"Value {value} is greater than maximum {self.max_value}"
            )
            raise ValidationError(message, field_name, value)


class LengthValidator(Validator):
    """
    Validates that a string's length is within specified bounds.

    YAML Example:
        validation:
          - type: length
            min: 3
            max: 50
            message: "Length must be between 3 and 50 characters"
    """

    def __init__(
        self,
        min_length: Optional[int] = None,
        max_length: Optional[int] = None,
        message: Optional[str] = None,
    ):
        """
        Initialize the length validator.

        Args:
            min_length: Minimum allowed length (inclusive).
            max_length: Maximum allowed length (inclusive).
            message: Custom error message.
        """
        super().__init__(message)
        if min_length is None and max_length is None:
            raise ValueError(
                "At least one of min_length or max_length must be provided"
            )
        self.min_length = min_length
        self.max_length = max_length

    def __call__(self, value: Any, field_name: str = None) -> None:
        if value is None:
            return

        length = len(str(value))

        if self.min_length is not None and length < self.min_length:
            message = self.get_error_message(
                f"Length {length} is less than minimum {self.min_length}"
            )
            raise ValidationError(message, field_name, value)

        if self.max_length is not None and length > self.max_length:
            message = self.get_error_message(
                f"Length {length} is greater than maximum {self.max_length}"
            )
            raise ValidationError(message, field_name, value)


class ChoiceValidator(Validator):
    """
    Validates that a value is one of the allowed choices.

    YAML Example:
        validation:
          - type: choice
            choices: ["active", "inactive", "pending"]
            case_sensitive: false
            message: "Status must be one of: active, inactive, pending"
    """

    def __init__(
        self,
        choices: List[Any],
        case_sensitive: bool = True,
        message: Optional[str] = None,
    ):
        """
        Initialize the choice validator.

        Args:
            choices: List of allowed values.
            case_sensitive: Whether string comparison is case-sensitive.
            message: Custom error message.
        """
        super().__init__(message)
        if not choices:
            raise ValueError("choices list cannot be empty")
        self.choices = choices
        self.case_sensitive = case_sensitive
        if not case_sensitive:
            self._normalized_choices = [
                c.lower() if isinstance(c, str) else c for c in choices
            ]
        else:
            self._normalized_choices = choices

    def __call__(self, value: Any, field_name: str = None) -> None:
        if value is None:
            return

        check_value = value
        if not self.case_sensitive and isinstance(value, str):
            check_value = value.lower()

        if check_value not in self._normalized_choices:
            message = self.get_error_message(
                f"Value '{value}' is not one of the allowed choices: {self.choices}"
            )
            raise ValidationError(message, field_name, value)


class NotEmptyValidator(Validator):
    """
    Validates that a value is not empty (empty string, empty list, etc.).

    YAML Example:
        validation:
          - type: not_empty
            message: "This field cannot be empty"
    """

    def __init__(self, message: Optional[str] = None):
        super().__init__(message)

    def __call__(self, value: Any, field_name: str = None) -> None:
        if value is None:
            return  # Let nullable validation handle None

        is_empty = False
        if isinstance(value, str):
            is_empty = value.strip() == ""
        elif hasattr(value, "__len__"):
            is_empty = len(value) == 0

        if is_empty:
            message = self.get_error_message("Value cannot be empty")
            raise ValidationError(message, field_name, value)


class CustomValidator(Validator):
    """
    Validates using a custom callable function.

    This validator wraps any callable that takes a value and returns
    True (valid) or False (invalid), or raises an exception.
    """

    def __init__(
        self,
        func: Callable[[Any], bool],
        message: Optional[str] = None,
    ):
        """
        Initialize the custom validator.

        Args:
            func: A callable that takes a value and returns True if valid.
            message: Custom error message.
        """
        super().__init__(message)
        if not callable(func):
            raise ValueError("func must be a callable")
        self.func = func

    def __call__(self, value: Any, field_name: str = None) -> None:
        if value is None:
            return

        try:
            result = self.func(value)
            if result is False:
                message = self.get_error_message("Custom validation failed for value")
                raise ValidationError(message, field_name, value)
        except ValidationError:
            raise
        except Exception as e:
            message = self.get_error_message(f"Custom validation failed: {str(e)}")
            raise ValidationError(message, field_name, value)


@dataclass
class FieldValidationConfig:
    """
    Configuration for field validation rules parsed from YAML.
    """

    field_name: str
    validators: List[Validator] = field(default_factory=list)

    def validate(self, value: Any) -> None:
        """
        Run all validators on the given value.

        Args:
            value: The value to validate.

        Raises:
            ValidationError: If any validator fails.
        """
        for validator in self.validators:
            validator(value, self.field_name)


# Registry mapping validator type names to validator classes
VALIDATOR_REGISTRY: Dict[str, type] = {
    "regex": RegexValidator,
    "range": RangeValidator,
    "length": LengthValidator,
    "choice": ChoiceValidator,
    "not_empty": NotEmptyValidator,
}


def create_validator_from_dict(config: Dict[str, Any]) -> Validator:
    """
    Create a validator instance from a dictionary configuration.

    Args:
        config: Dictionary with validator configuration.
            Must include 'type' key matching a registered validator.

    Returns:
        A configured Validator instance.

    Raises:
        ValueError: If validator type is unknown or configuration is invalid.

    YAML Example:
        validation:
          - type: regex
            pattern: "^[A-Z]{3}$"
          - type: range
            min: 0
            max: 100
    """
    if "type" not in config:
        raise ValueError("Validator configuration must include 'type'")

    validator_type = config["type"]
    if validator_type not in VALIDATOR_REGISTRY:
        raise ValueError(
            f"Unknown validator type '{validator_type}'. "
            f"Available types: {list(VALIDATOR_REGISTRY.keys())}"
        )

    validator_class = VALIDATOR_REGISTRY[validator_type]
    kwargs = {k: v for k, v in config.items() if k != "type"}

    # Map YAML keys to constructor parameters
    key_mapping = {
        "pattern": "pattern",
        "flags": "flags",
        "min": "min_value",
        "max": "max_value",
        "min_length": "min_length",
        "max_length": "max_length",
        "choices": "choices",
        "case_sensitive": "case_sensitive",
        "message": "message",
    }

    mapped_kwargs = {}
    for yaml_key, value in kwargs.items():
        if yaml_key in key_mapping:
            mapped_kwargs[key_mapping[yaml_key]] = value
        else:
            # Pass through any additional kwargs
            mapped_kwargs[yaml_key] = value

    try:
        validator: Validator = validator_class(**mapped_kwargs)
        return validator
    except TypeError as e:
        raise ValueError(f"Invalid configuration for {validator_type} validator: {e}")


def parse_field_validators(
    field_name: str, validation_config: List[Dict[str, Any]]
) -> FieldValidationConfig:
    """
    Parse validation configuration for a single field.

    Args:
        field_name: Name of the field being validated.
        validation_config: List of validator configurations from YAML.

    Returns:
        FieldValidationConfig with all validators for the field.
    """
    validators = []
    for config in validation_config:
        try:
            validator = create_validator_from_dict(config)
            validators.append(validator)
        except ValueError as e:
            logger.warning(f"Failed to create validator for field '{field_name}': {e}")
            raise

    return FieldValidationConfig(field_name=field_name, validators=validators)
