"""Schema validator for VibeDB - validates documents against schemas."""

from typing import Dict, Any, List, Optional, TYPE_CHECKING

from vibedb.errors import ValidationError
from vibedb.schema_validation.constraints import check_constraint

if TYPE_CHECKING:
    from vibedb.core.collection import Collection


class SchemaValidator:
    """
    Validates documents against a schema definition.

    Schema format:
    {
        "field_name": {
            "type": "string" | "integer" | "number" | "boolean" | "array" | "object",
            "required": True | False,
            "min": <value>,  # Min value/length
            "max": <value>,  # Max value/length
            "enum": [<allowed values>],
            "pattern": "<regex>",
            "default": <value>,
            "unique": True | False,
        }
    }

    Example:
        >>> schema = {
        ...     "name": {"type": "string", "required": True, "min": 1},
        ...     "age": {"type": "integer", "min": 0, "max": 150},
        ...     "email": {"type": "string", "pattern": r"^[^@]+@[^@]+\\.[^@]+$"},
        ...     "tags": {"type": "array"},
        ... }
        >>> validator = SchemaValidator(schema)
        >>> validator.validate({"name": "Alice", "age": 28})  # OK
        >>> validator.validate({"age": -5})  # Raises ValidationError
    """

    def __init__(self, schema: Dict[str, Dict[str, Any]], collection: "Collection" = None):
        """
        Initialize schema validator.

        Args:
            schema: Schema definition dict
            collection: Optional collection (for unique checks)
        """
        self.schema = schema
        self.collection = collection

    def validate(self, document: Dict[str, Any], partial: bool = False) -> Dict[str, Any]:
        """
        Validate a document against the schema.

        Args:
            document: Document to validate
            partial: If True, only validate present fields (for updates)

        Returns:
            dict: Validated document (with defaults applied)

        Raises:
            ValidationError: If validation fails

        Example:
            >>> validator.validate({"name": "Alice", "age": 28})
            {'name': 'Alice', 'age': 28, '_createdAt': ..., ...}
        """
        # Start with copy of document
        validated_doc = document.copy()

        # Check required fields (unless partial update)
        if not partial:
            self._check_required_fields(validated_doc)

        # Apply defaults for missing fields (unless partial update)
        if not partial:
            validated_doc = self._apply_defaults(validated_doc)

        # Validate each field
        for field, value in validated_doc.items():
            # Skip internal fields
            if field.startswith('_'):
                continue

            # Get field schema
            field_schema = self.schema.get(field)

            if field_schema is None:
                # Field not in schema - allow extra fields by default
                continue

            # Validate field
            self._validate_field(field, value, field_schema)

        return validated_doc

    def _check_required_fields(self, document: Dict[str, Any]):
        """
        Check that all required fields are present.

        Args:
            document: Document to check

        Raises:
            ValidationError: If required field is missing
        """
        for field, field_schema in self.schema.items():
            if field_schema.get('required', False):
                if field not in document:
                    raise ValidationError(
                        field=field,
                        constraint='required',
                        value=None,
                        message=f"Field '{field}' is required"
                    )

    def _apply_defaults(self, document: Dict[str, Any]) -> Dict[str, Any]:
        """
        Apply default values for missing fields.

        Args:
            document: Document to apply defaults to

        Returns:
            dict: Document with defaults applied
        """
        result = document.copy()

        for field, field_schema in self.schema.items():
            if field not in result and 'default' in field_schema:
                result[field] = field_schema['default']

        return result

    def _validate_field(
        self,
        field: str,
        value: Any,
        field_schema: Dict[str, Any]
    ):
        """
        Validate a single field.

        Args:
            field: Field name
            value: Field value
            field_schema: Field schema definition

        Raises:
            ValidationError: If validation fails
        """
        # Check each constraint
        for constraint_name, constraint_value in field_schema.items():
            # Skip non-constraint keys
            if constraint_name in ('default', 'items'):
                continue

            # Handle unique constraint separately
            if constraint_name == 'unique' and constraint_value:
                self._check_unique(field, value)
                continue

            # Check constraint
            is_valid, error = check_constraint(
                constraint_name,
                value,
                constraint_value,
                field
            )

            if not is_valid:
                raise ValidationError(
                    field=field,
                    constraint=constraint_name,
                    value=value,
                    message=error
                )

        # Validate array items if specified
        if field_schema.get('type') == 'array' and 'items' in field_schema:
            self._validate_array_items(field, value, field_schema['items'])

    def _validate_array_items(
        self,
        field: str,
        value: List[Any],
        items_schema: Dict[str, Any]
    ):
        """
        Validate array items against item schema.

        Args:
            field: Array field name
            value: Array value
            items_schema: Schema for array items

        Raises:
            ValidationError: If any item is invalid
        """
        if not isinstance(value, list):
            return

        for i, item in enumerate(value):
            # Check each constraint on items
            for constraint_name, constraint_value in items_schema.items():
                is_valid, error = check_constraint(
                    constraint_name,
                    item,
                    constraint_value,
                    f"{field}[{i}]"
                )

                if not is_valid:
                    raise ValidationError(
                        field=f"{field}[{i}]",
                        constraint=constraint_name,
                        value=item,
                        message=error
                    )

    def _check_unique(self, field: str, value: Any):
        """
        Check if field value is unique in collection.

        Args:
            field: Field name
            value: Field value

        Raises:
            ValidationError: If value is not unique
        """
        if self.collection is None:
            # Can't check uniqueness without collection
            return

        # Check if value already exists
        existing = self.collection.find_one({field: value})
        if existing:
            raise ValidationError(
                field=field,
                constraint='unique',
                value=value,
                message=f"Field '{field}' must be unique, value '{value}' already exists"
            )


def validate_schema_definition(schema: Dict[str, Any]) -> bool:
    """
    Validate that a schema definition is well-formed.

    Args:
        schema: Schema definition to validate

    Returns:
        bool: True if schema is valid

    Raises:
        ValueError: If schema is invalid

    Example:
        >>> schema = {"name": {"type": "string"}}
        >>> validate_schema_definition(schema)
        True
    """
    if not isinstance(schema, dict):
        raise ValueError("Schema must be a dictionary")

    valid_constraints = {
        'type', 'required', 'min', 'max', 'enum',
        'pattern', 'default', 'unique', 'items'
    }

    valid_types = {
        'string', 'integer', 'number', 'boolean',
        'array', 'object', 'null'
    }

    for field, field_schema in schema.items():
        if not isinstance(field_schema, dict):
            raise ValueError(f"Schema for field '{field}' must be a dictionary")

        # Check type if specified
        if 'type' in field_schema:
            field_type = field_schema['type']
            if field_type not in valid_types:
                raise ValueError(
                    f"Invalid type '{field_type}' for field '{field}'. "
                    f"Must be one of {valid_types}"
                )

        # Check for unknown constraints
        unknown = set(field_schema.keys()) - valid_constraints
        if unknown:
            raise ValueError(
                f"Unknown constraints for field '{field}': {unknown}"
            )

    return True
