"""
Decorator-based validator injection for AtomSQL fields.

This module provides decorators and utilities to inject YAML-defined
validation rules into AtomSQL field validation.
"""

import functools
import logging
from typing import Any, Dict, List, Type, TYPE_CHECKING

from atomingest.schema.validators import (
    FieldValidationConfig,
    ValidationError,
    Validator,
    parse_field_validators,
)

if TYPE_CHECKING:
    from atomsql.orm.fields import Field
    from atomsql.orm.models import Model

logger = logging.getLogger(__name__)


def field_validator(*validators: Validator):
    """
    Decorator to add validators to a field's validation chain.

    This decorator wraps the field's __set__ method to run additional
    validators before the value is set.

    Example:
        @field_validator(
            RegexValidator(r'^[A-Z]{3}$'),
            RangeValidator(min_value=0, max_value=100)
        )
        class CustomField(CharField):
            pass

    Args:
        *validators: Validator instances to apply.

    Returns:
        A decorator function that wraps the field class.
    """

    def decorator(field_class: Type["Field"]) -> Type["Field"]:
        original_set = field_class.__set__

        @functools.wraps(original_set)
        def wrapped_set(self, instance, value):
            # Run custom validators first (before type validation)
            if value is not None:
                for validator in validators:
                    validator(value, self.name)
            # Then run the original __set__ method
            return original_set(self, instance, value)

        field_class.__set__ = wrapped_set
        # Store validators on the class for introspection
        if not hasattr(field_class, "_validators"):
            field_class._validators = []
        field_class._validators.extend(validators)
        return field_class

    return decorator


def inject_validators(
    field_instance: "Field",
    validators: List[Validator],
) -> "Field":
    """
    Inject validators into an existing field instance.

    This function wraps the field's __set__ method to run additional
    validators. Unlike the decorator approach, this works on instances.

    Args:
        field_instance: The field instance to modify.
        validators: List of Validator instances to apply.

    Returns:
        The modified field instance.

    Example:
        email_field = EmailField()
        inject_validators(email_field, [
            RegexValidator(r"^[a-z]+@company\\.com$", message="Must be company email")
        ])
    """
    if not validators:
        return field_instance

    # Store the original __set__ method from the instance or class
    original_set = field_instance.__class__.__set__

    # Create a new __set__ method that includes validation
    def validated_set(self, instance, value):
        if value is not None:
            for validator in validators:
                validator(value, self.name)
        return original_set(self, instance, value)

    # Create a subclass dynamically to override __set__
    original_class = field_instance.__class__
    new_class_name = f"Validated{original_class.__name__}"

    # Check if we've already created this validated class
    new_class = type(
        new_class_name,
        (original_class,),
        {
            "__set__": validated_set,
            "_injected_validators": validators,
        },
    )

    # Change the instance's class to the new validated class
    field_instance.__class__ = new_class

    return field_instance


def create_validated_model(
    model_class: Type["Model"],
    validation_configs: Dict[str, FieldValidationConfig],
) -> Type["Model"]:
    """
    Create a new model class with validation injected into fields.

    This creates a subclass of the original model where fields have
    YAML-defined validators injected.

    Args:
        model_class: The original model class.
        validation_configs: Dictionary mapping field names to FieldValidationConfig.

    Returns:
        A new model class with validated fields.
    """
    if not validation_configs:
        return model_class

    # Create new field instances with injected validators
    new_attrs = {"__module__": model_class.__module__}

    for attr_name in dir(model_class):
        attr = getattr(model_class, attr_name)
        if hasattr(attr, "validate_type"):  # It's a Field
            if attr_name in validation_configs:
                config = validation_configs[attr_name]
                # Clone the field and inject validators
                field_copy = _clone_field(attr)
                inject_validators(field_copy, config.validators)
                new_attrs[attr_name] = field_copy
                logger.debug(
                    f"Injected {len(config.validators)} validators into field '{attr_name}'"
                )

    # Create the new model class
    validated_class_name = f"Validated{model_class.__name__}"
    validated_class = type(validated_class_name, (model_class,), new_attrs)

    logger.info(
        f"Created validated model '{validated_class_name}' with "
        f"{len(validation_configs)} validated fields"
    )

    return validated_class


def _clone_field(field: "Field") -> "Field":
    """
    Create a shallow clone of a field instance.

    Args:
        field: The field to clone.

    Returns:
        A new field instance with the same configuration.
    """
    field_class = field.__class__

    # Get the field's init parameters by inspecting its attributes
    init_kwargs = {}

    # Common field attributes
    if hasattr(field, "default"):
        init_kwargs["default"] = field.default
    if hasattr(field, "unique"):
        init_kwargs["unique"] = field.unique
    if hasattr(field, "nullable"):
        init_kwargs["nullable"] = field.nullable

    # CharField-specific
    if hasattr(field, "max_length"):
        init_kwargs["max_length"] = field.max_length

    # Create the new field
    new_field = field_class(**init_kwargs)

    # Copy over the name if set
    if field.name:
        new_field.name = field.name

    return new_field


class ValidatedFieldDescriptor:
    """
    A descriptor that wraps a field to add validation.

    This provides an alternative to modifying __set__ directly,
    using the descriptor protocol for cleaner separation.
    """

    def __init__(self, field: "Field", validators: List[Validator]):
        self.field = field
        self.validators = validators
        self.name = None

    def __set_name__(self, owner, name):
        self.name = name
        self.field.__set_name__(owner, name)

    def __get__(self, instance, owner):
        return self.field.__get__(instance, owner)

    def __set__(self, instance, value):
        # Run custom validators first
        if value is not None:
            for validator in self.validators:
                validator(value, self.name)
        # Then delegate to the original field
        self.field.__set__(instance, value)


def apply_validation_to_schema(
    schema: Dict[str, "Field"],
    validation_rules: Dict[str, List[Dict[str, Any]]],
) -> Dict[str, "Field"]:
    """
    Apply validation rules from YAML to a schema dictionary.

    This is the main entry point for integrating YAML-defined validation
    with the SchemaLoader.

    Args:
        schema: Dictionary mapping field names to Field instances.
        validation_rules: Dictionary mapping field names to lists of
            validator configurations from YAML.

    Returns:
        Modified schema with validators injected into fields.

    Example YAML:
        schema:
          account_code:
            type: CharField
            max_length: 10
            validation:
              - type: regex
                pattern: "^[A-Z]{2}[0-9]{6}$"
                message: "Invalid account code format"
              - type: not_empty
    """
    for field_name, validators_config in validation_rules.items():
        if field_name not in schema:
            logger.warning(f"Validation rules defined for unknown field '{field_name}'")
            continue

        field = schema[field_name]
        try:
            field_config = parse_field_validators(field_name, validators_config)
            inject_validators(field, field_config.validators)
            logger.debug(
                f"Applied {len(field_config.validators)} validators to '{field_name}'"
            )
        except ValueError as e:
            logger.error(f"Failed to apply validation to field '{field_name}': {e}")
            raise

    return schema


def validate_row(
    row: Dict[str, Any],
    validation_configs: Dict[str, FieldValidationConfig],
) -> List[ValidationError]:
    """
    Validate a data row against field validation configurations.

    This is useful for validating data before it's inserted into the database,
    collecting all validation errors rather than failing on the first one.

    Args:
        row: Dictionary of field names to values.
        validation_configs: Dictionary of field names to FieldValidationConfig.

    Returns:
        List of ValidationErrors (empty if all validations pass).
    """
    errors = []
    for field_name, config in validation_configs.items():
        value = row.get(field_name)
        try:
            config.validate(value)
        except ValidationError as e:
            errors.append(e)
    return errors


def validate_row_strict(
    row: Dict[str, Any],
    validation_configs: Dict[str, FieldValidationConfig],
) -> None:
    """
    Validate a data row strictly, raising on the first error.

    Args:
        row: Dictionary of field names to values.
        validation_configs: Dictionary of field names to FieldValidationConfig.

    Raises:
        ValidationError: If any field fails validation.
    """
    for field_name, config in validation_configs.items():
        value = row.get(field_name)
        config.validate(value)
