"""
Schema validation for automation input/output.

This module provides JSON Schema validation for automation
input and output data.
"""

from dataclasses import dataclass
from typing import Any


@dataclass
class SchemaValidationError:
    """A schema validation error."""

    path: str
    message: str
    value: Any

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "path": self.path,
            "message": self.message,
            "value": self.value,
        }


@dataclass
class SchemaValidationResult:
    """Result of schema validation."""

    valid: bool
    errors: list[SchemaValidationError]

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "valid": self.valid,
            "errors": [e.to_dict() for e in self.errors],
        }


class SchemaValidator:
    """
    JSON Schema validator for automation data.

    This validator checks input and output data against JSON Schema
    definitions in the automation manifest.

    Example:
        validator = SchemaValidator()

        schema = {
            "type": "object",
            "properties": {
                "email": {"type": "string", "format": "email"},
                "count": {"type": "integer", "minimum": 1}
            },
            "required": ["email"]
        }

        result = validator.validate({"email": "test@example.com"}, schema)
        assert result.valid
    """

    def validate(
        self,
        data: Any,
        schema: dict[str, Any],
    ) -> SchemaValidationResult:
        """
        Validate data against a JSON Schema.

        Args:
            data: Data to validate
            schema: JSON Schema definition

        Returns:
            Validation result with errors
        """
        errors: list[SchemaValidationError] = []
        self._validate_recursive(data, schema, "", errors)

        return SchemaValidationResult(
            valid=len(errors) == 0,
            errors=errors,
        )

    def _validate_recursive(
        self,
        data: Any,
        schema: dict[str, Any],
        path: str,
        errors: list[SchemaValidationError],
    ) -> None:
        """Recursively validate data against schema."""
        schema_type = schema.get("type")

        # Type validation
        if schema_type:
            if not self._check_type(data, schema_type):
                errors.append(
                    SchemaValidationError(
                        path=path or "$",
                        message=f"Expected {schema_type}, got {type(data).__name__}",
                        value=data,
                    )
                )
                return

        # Object validation
        if schema_type == "object":
            self._validate_object(data, schema, path, errors)

        # Array validation
        elif schema_type == "array":
            self._validate_array(data, schema, path, errors)

        # Number constraints
        elif schema_type in ("number", "integer"):
            self._validate_number(data, schema, path, errors)

        # String constraints
        elif schema_type == "string":
            self._validate_string(data, schema, path, errors)

    def _check_type(self, data: Any, expected_type: str) -> bool:
        """Check if data matches expected JSON Schema type."""
        type_map = {
            "string": str,
            "number": (int, float),
            "integer": int,
            "boolean": bool,
            "array": list,
            "object": dict,
            "null": type(None),
        }

        expected = type_map.get(expected_type)
        if expected is None:
            return True  # Unknown type, skip validation

        return isinstance(data, expected)

    def _validate_object(
        self,
        data: dict[str, Any],
        schema: dict[str, Any],
        path: str,
        errors: list[SchemaValidationError],
    ) -> None:
        """Validate object properties."""
        properties = schema.get("properties", {})
        required = schema.get("required", [])

        # Check required properties
        for prop in required:
            if prop not in data:
                errors.append(
                    SchemaValidationError(
                        path=f"{path}.{prop}" if path else prop,
                        message=f"Required property '{prop}' is missing",
                        value=None,
                    )
                )

        # Validate each property
        for prop, value in data.items():
            prop_path = f"{path}.{prop}" if path else prop
            if prop in properties:
                self._validate_recursive(value, properties[prop], prop_path, errors)

    def _validate_array(
        self,
        data: list[Any],
        schema: dict[str, Any],
        path: str,
        errors: list[SchemaValidationError],
    ) -> None:
        """Validate array items."""
        items_schema = schema.get("items")
        min_items = schema.get("minItems")
        max_items = schema.get("maxItems")

        if min_items is not None and len(data) < min_items:
            errors.append(
                SchemaValidationError(
                    path=path,
                    message=f"Array must have at least {min_items} items",
                    value=len(data),
                )
            )

        if max_items is not None and len(data) > max_items:
            errors.append(
                SchemaValidationError(
                    path=path,
                    message=f"Array must have at most {max_items} items",
                    value=len(data),
                )
            )

        if items_schema:
            for i, item in enumerate(data):
                item_path = f"{path}[{i}]"
                self._validate_recursive(item, items_schema, item_path, errors)

    def _validate_number(
        self,
        data: int | float,
        schema: dict[str, Any],
        path: str,
        errors: list[SchemaValidationError],
    ) -> None:
        """Validate number constraints."""
        minimum = schema.get("minimum")
        maximum = schema.get("maximum")

        if minimum is not None and data < minimum:
            errors.append(
                SchemaValidationError(
                    path=path,
                    message=f"Value must be >= {minimum}",
                    value=data,
                )
            )

        if maximum is not None and data > maximum:
            errors.append(
                SchemaValidationError(
                    path=path,
                    message=f"Value must be <= {maximum}",
                    value=data,
                )
            )

    def _validate_string(
        self,
        data: str,
        schema: dict[str, Any],
        path: str,
        errors: list[SchemaValidationError],
    ) -> None:
        """Validate string constraints."""
        min_length = schema.get("minLength")
        max_length = schema.get("maxLength")
        pattern = schema.get("pattern")

        if min_length is not None and len(data) < min_length:
            errors.append(
                SchemaValidationError(
                    path=path,
                    message=f"String must be at least {min_length} characters",
                    value=data,
                )
            )

        if max_length is not None and len(data) > max_length:
            errors.append(
                SchemaValidationError(
                    path=path,
                    message=f"String must be at most {max_length} characters",
                    value=data,
                )
            )

        if pattern:
            import re

            if not re.match(pattern, data):
                errors.append(
                    SchemaValidationError(
                        path=path,
                        message=f"String must match pattern: {pattern}",
                        value=data,
                    )
                )

    def validate_input(
        self,
        input_data: dict[str, Any],
        schema: dict[str, Any],
    ) -> SchemaValidationResult:
        """
        Validate automation input data against a schema.

        This is a convenience wrapper around validate() specifically
        for validating input data.

        Args:
            input_data: Input data to validate
            schema: JSON Schema definition for input

        Returns:
            Validation result with errors

        Example:
            validator = SchemaValidator()
            input_schema = {
                "type": "object",
                "properties": {
                    "query": {"type": "string"},
                    "limit": {"type": "integer", "minimum": 1}
                },
                "required": ["query"]
            }

            result = validator.validate_input(
                {"query": "search term", "limit": 10},
                input_schema
            )
            assert result.valid
        """
        return self.validate(input_data, schema)

    def validate_output(
        self,
        output_data: dict[str, Any],
        schema: dict[str, Any],
    ) -> SchemaValidationResult:
        """
        Validate automation output data against a schema.

        This is a convenience wrapper around validate() specifically
        for validating output data.

        Args:
            output_data: Output data to validate
            schema: JSON Schema definition for output

        Returns:
            Validation result with errors

        Example:
            validator = SchemaValidator()
            output_schema = {
                "type": "object",
                "properties": {
                    "results": {"type": "array", "items": {"type": "string"}},
                    "total": {"type": "integer"}
                },
                "required": ["results"]
            }

            result = validator.validate_output(
                {"results": ["item1", "item2"], "total": 2},
                output_schema
            )
            assert result.valid
        """
        return self.validate(output_data, schema)
