"""
================================================================================
IMMUTABLE FILE - DO NOT MODIFY
================================================================================
This file is protected by git pre-commit hooks.
Reason: Core validation pipeline - ensures design system data integrity
Last Modified: 2025-12-09
To update: Use 'DSS_IMMUTABLE_BYPASS=1 git commit -m "[IMMUTABLE-UPDATE] reason"'
================================================================================

Project Validation Pipeline

A comprehensive 4-stage validation system ensuring design system data integrity.
This validator checks JSON structure, required fields, token references, and
component dependencies before processing.

Stages:
1. Schema validation - JSON structure validation using Pydantic models
2. Structure validation - Required fields and organizational structure
3. Token validation - Token value types, categories, and inter-token references
4. Component validation - Component properties, variants, and dependencies
"""

from enum import Enum
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field, ConfigDict, ValidationError as PydanticValidationError

from dss.models.project import Project
from dss.models.theme import Theme, DesignToken, TokenCategory
from dss.models.component import Component


class ValidationStage(str, Enum):
    """Validation pipeline stages"""
    SCHEMA = "schema"
    STRUCTURE = "structure"
    TOKEN_VALIDATION = "token_validation"
    COMPONENT_VALIDATION = "component_validation"
    COMPLETE = "complete"


class ValidationError(BaseModel):
    """Single validation error from the pipeline."""
    model_config = ConfigDict(arbitrary_types_allowed=True)

    stage: ValidationStage = Field(..., description="Validation stage where error occurred")
    message: str = Field(..., description="Error description")
    field: Optional[str] = Field(None, description="Field path where error occurred")

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


class ValidationResult(BaseModel):
    """Complete result from validation pipeline."""
    model_config = ConfigDict(arbitrary_types_allowed=True)

    is_valid: bool = Field(..., description="Whether validation passed without errors")
    stage: ValidationStage = Field(..., description="Which validation stage completed")
    errors: List[ValidationError] = Field(default_factory=list, description="All validation errors detected")

    def add_error(self, stage: ValidationStage, message: str, field: Optional[str] = None):
        """Add validation error"""
        self.errors.append(ValidationError(stage=stage, message=message, field=field))
        self.is_valid = False


class ProjectValidator:
    """
    4-stage validation pipeline for DSS projects

    Stage 1: Schema validation (JSON structure)
    Stage 2: Structure validation (required fields)
    Stage 3: Token validation (types, references)
    Stage 4: Component validation (props, variants, dependencies)
    """

    def validate(self, data: Dict[str, Any]) -> ValidationResult:
        """
        Run full immune system validation pipeline

        The DSS immune system antibodies check the data through 4 stages,
        detecting pathogens (invalid data) before they infect the organism.

        Args:
            data: Raw project data (incoming nutrition/nutrients)

        Returns:
            ValidationResult with health status and any detected infections
        """
        result = ValidationResult(is_valid=True, stage=ValidationStage.SCHEMA, errors=[])

        # Stage 1: Schema validation
        if not self._validate_schema(data, result):
            return result

        result.stage = ValidationStage.STRUCTURE

        # Stage 2: Structure validation
        if not self._validate_structure(data, result):
            return result

        result.stage = ValidationStage.TOKEN_VALIDATION

        # Stage 3: Token validation
        if not self._validate_tokens(data, result):
            return result

        result.stage = ValidationStage.COMPONENT_VALIDATION

        # Stage 4: Component validation
        if not self._validate_components(data, result):
            return result

        result.stage = ValidationStage.COMPLETE
        return result

    def _validate_schema(self, data: Dict[str, Any], result: ValidationResult) -> bool:
        """
        Stage 1: Schema Validation - Validate JSON structure using Pydantic models.

        Returns:
            True if JSON structure is valid, False otherwise
        """
        try:
            # Try to parse as Pydantic Project model
            Project(**data)
            return True
        except PydanticValidationError as e:
            # Extract Pydantic errors
            for error in e.errors():
                field = ".".join(str(loc) for loc in error["loc"])
                message = error["msg"]
                result.add_error(ValidationStage.SCHEMA, message, field)
            return False
        except Exception as e:
            result.add_error(ValidationStage.SCHEMA, f"Schema validation failed: {str(e)}")
            return False

    def _validate_structure(self, data: Dict[str, Any], result: ValidationResult) -> bool:
        """
        Stage 2: Structure Validation - Validate required fields and organizational structure.

        Returns:
            True if required structure is present, False if missing
        """
        # Check required top-level fields
        required_fields = ["id", "name", "theme"]
        for field in required_fields:
            if field not in data or not data[field]:
                result.add_error(
                    ValidationStage.STRUCTURE,
                    f"Required field '{field}' is missing or empty",
                    field
                )

        # Check theme structure
        if "theme" in data:
            theme_data = data["theme"]
            if not isinstance(theme_data, dict):
                result.add_error(
                    ValidationStage.STRUCTURE,
                    "Theme must be an object",
                    "theme"
                )
            elif "name" not in theme_data:
                result.add_error(
                    ValidationStage.STRUCTURE,
                    "Theme must have a name",
                    "theme.name"
                )

        return result.is_valid

    def _validate_tokens(self, data: Dict[str, Any], result: ValidationResult) -> bool:
        """
        Stage 3: Token Validation - Validate token values, types, and references.

        Returns:
            True if tokens are valid, False if errors found
        """
        if "theme" not in data or "tokens" not in data["theme"]:
            return True  # No tokens to validate

        tokens = data["theme"]["tokens"]
        if not isinstance(tokens, dict):
            result.add_error(
                ValidationStage.TOKEN_VALIDATION,
                "Tokens must be defined as an object",
                "theme.tokens"
            )
            return False

        # Validate each token
        for token_name, token_data in tokens.items():
            # Check token structure
            if not isinstance(token_data, dict):
                result.add_error(
                    ValidationStage.TOKEN_VALIDATION,
                    f"Token must be defined as an object",
                    f"theme.tokens.{token_name}"
                )
                continue

            value = token_data.get("value", "")
            if not value:
                result.add_error(
                    ValidationStage.TOKEN_VALIDATION,
                    "Token value cannot be empty",
                    f"theme.tokens.{token_name}.value"
                )

            # Check token references (format: {token-name})
            if isinstance(value, str) and value.startswith("{") and value.endswith("}"):
                referenced_token = value[1:-1]  # Remove { }
                if referenced_token not in tokens:
                    result.add_error(
                        ValidationStage.TOKEN_VALIDATION,
                        f"Referenced token '{referenced_token}' does not exist",
                        f"theme.tokens.{token_name}.value"
                    )

            # Validate category is a valid enum value
            category = token_data.get("category")
            if category:
                try:
                    TokenCategory(category)
                except ValueError:
                    valid_categories = [c.value for c in TokenCategory]
                    result.add_error(
                        ValidationStage.TOKEN_VALIDATION,
                        f"Category '{category}' is invalid. Valid options: {', '.join(valid_categories)}",
                        f"theme.tokens.{token_name}.category"
                    )

        return result.is_valid

    def _validate_components(self, data: Dict[str, Any], result: ValidationResult) -> bool:
        """
        Stage 4: Component Validation - Validate component props, variants, and dependencies.

        Returns:
            True if components are valid, False if errors found
        """
        if "components" not in data:
            return True  # No components to validate

        components = data["components"]
        if not isinstance(components, list):
            result.add_error(
                ValidationStage.COMPONENT_VALIDATION,
                "Components must be defined as an array",
                "components"
            )
            return False

        # Build component name index
        component_names = set()
        for i, comp in enumerate(components):
            if not isinstance(comp, dict):
                result.add_error(
                    ValidationStage.COMPONENT_VALIDATION,
                    "Component must be defined as an object",
                    f"components[{i}]"
                )
                continue

            comp_name = comp.get("name")
            if comp_name:
                component_names.add(comp_name)

        # Validate component dependencies
        for i, comp in enumerate(components):
            if not isinstance(comp, dict):
                continue

            comp_name = comp.get("name", f"components[{i}]")
            dependencies = comp.get("dependencies", [])

            if not isinstance(dependencies, list):
                result.add_error(
                    ValidationStage.COMPONENT_VALIDATION,
                    "Dependencies must be defined as an array",
                    f"{comp_name}.dependencies"
                )
                continue

            # Check each dependency exists
            for dep in dependencies:
                if dep not in component_names:
                    result.add_error(
                        ValidationStage.COMPONENT_VALIDATION,
                        f"Dependency '{dep}' does not exist",
                        f"{comp_name}.dependencies"
                    )

        return result.is_valid
