"""
Schema Validator - Validate configuration against JSON schema

Validates groknroll configuration files against a predefined schema,
providing clear error messages with context.

Examples:
    validator = SchemaValidator()
    result = validator.validate(config)
    if not result.valid:
        for error in result.errors:
            print(f"Error at {error.path}: {error.message}")
"""

import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional

from jsonschema import Draft7Validator, ValidationError
from jsonschema.exceptions import SchemaError


@dataclass
class ValidationErrorInfo:
    """
    Information about a validation error

    Attributes:
        path: JSON path to the error (e.g., "provider.openai.apiKey")
        message: Human-readable error message
        schema_path: Path in schema that failed
        value: The invalid value (if available)
    """

    path: str
    message: str
    schema_path: str = ""
    value: Any = None

    def __str__(self) -> str:
        if self.path:
            return f"{self.path}: {self.message}"
        return self.message


@dataclass
class ValidationResult:
    """
    Result of schema validation

    Attributes:
        valid: Whether the config is valid
        errors: List of validation errors
        warnings: List of validation warnings
        schema_version: Version of schema used
    """

    valid: bool = True
    errors: list[ValidationErrorInfo] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)
    schema_version: str = "draft-07"


class SchemaValidator:
    """
    Validate configuration against JSON schema

    Validates groknroll.json configurations and provides
    clear error messages for invalid configurations.

    Example:
        validator = SchemaValidator()

        config = {
            "model": "anthropic/claude-sonnet-4-5",
            "permission": {"*": "ask"}
        }

        result = validator.validate(config)
        # result.valid = True

        config = {"model": 123}  # Wrong type
        result = validator.validate(config)
        # result.valid = False
        # result.errors[0].message = "123 is not of type 'string'"
    """

    # Default schema path (relative to this module)
    DEFAULT_SCHEMA_PATH = Path(__file__).parent / "schema.json"

    def __init__(
        self,
        schema_path: Optional[Path] = None,
        strict: bool = False,
    ):
        """
        Initialize SchemaValidator

        Args:
            schema_path: Path to JSON schema file (uses built-in if None)
            strict: If True, treat warnings as errors
        """
        self.schema_path = schema_path or self.DEFAULT_SCHEMA_PATH
        self.strict = strict
        self._schema: Optional[dict[str, Any]] = None
        self._validator: Optional[Draft7Validator] = None

    @property
    def schema(self) -> dict[str, Any]:
        """Load and cache the schema"""
        if self._schema is None:
            self._schema = self._load_schema()
        return self._schema

    @property
    def validator(self) -> Draft7Validator:
        """Get or create the validator"""
        if self._validator is None:
            self._validator = Draft7Validator(self.schema)
        return self._validator

    def _load_schema(self) -> dict[str, Any]:
        """Load schema from file"""
        try:
            with open(self.schema_path, encoding="utf-8") as f:
                return json.load(f)
        except FileNotFoundError:
            raise FileNotFoundError(f"Schema file not found: {self.schema_path}")
        except json.JSONDecodeError as e:
            raise ValueError(f"Invalid schema JSON: {e}")

    def validate(self, config: dict[str, Any]) -> ValidationResult:
        """
        Validate configuration against schema

        Args:
            config: Configuration dict to validate

        Returns:
            ValidationResult with validation status and errors
        """
        errors: list[ValidationErrorInfo] = []
        warnings: list[str] = []

        try:
            # Collect all validation errors
            for error in self.validator.iter_errors(config):
                error_info = self._format_error(error)
                errors.append(error_info)

        except SchemaError as e:
            errors.append(
                ValidationErrorInfo(
                    path="$schema",
                    message=f"Invalid schema: {e.message}",
                )
            )

        # Additional semantic checks
        semantic_warnings = self._check_semantics(config)
        if self.strict:
            for warning in semantic_warnings:
                errors.append(
                    ValidationErrorInfo(
                        path="",
                        message=warning,
                    )
                )
        else:
            warnings.extend(semantic_warnings)

        return ValidationResult(
            valid=len(errors) == 0,
            errors=errors,
            warnings=warnings,
            schema_version="draft-07",
        )

    def _format_error(self, error: ValidationError) -> ValidationErrorInfo:
        """
        Format a validation error into a user-friendly message

        Args:
            error: jsonschema ValidationError

        Returns:
            ValidationErrorInfo with formatted message
        """
        # Build JSON path from error path
        path = ".".join(str(p) for p in error.absolute_path) if error.absolute_path else ""

        # Build schema path
        schema_path = ".".join(str(p) for p in error.absolute_schema_path)

        # Create user-friendly message
        message = self._make_friendly_message(error)

        return ValidationErrorInfo(
            path=path,
            message=message,
            schema_path=schema_path,
            value=error.instance if error.absolute_path else None,
        )

    def _make_friendly_message(self, error: ValidationError) -> str:
        """
        Create a user-friendly error message

        Args:
            error: jsonschema ValidationError

        Returns:
            Human-readable error message
        """
        # Type errors
        if error.validator == "type":
            expected = error.validator_value
            actual = type(error.instance).__name__
            if isinstance(error.instance, str) and len(error.instance) > 20:
                value = f'"{error.instance[:20]}..."'
            elif isinstance(error.instance, str):
                value = f'"{error.instance}"'
            else:
                value = repr(error.instance)
            return f"Expected {expected}, got {actual}: {value}"

        # Enum errors
        if error.validator == "enum":
            options = ", ".join(repr(v) for v in error.validator_value)
            return f"Must be one of: {options}"

        # Pattern errors
        if error.validator == "pattern":
            return f"Does not match pattern: {error.validator_value}"

        # Required errors
        if error.validator == "required":
            missing = list(error.validator_value)
            return f"Missing required field(s): {', '.join(missing)}"

        # Min/max errors
        if error.validator == "minimum":
            return f"Value must be >= {error.validator_value}"
        if error.validator == "maximum":
            return f"Value must be <= {error.validator_value}"
        if error.validator == "minLength":
            return f"String must be at least {error.validator_value} character(s)"
        if error.validator == "maxLength":
            return f"String must be at most {error.validator_value} character(s)"

        # Additional properties
        if error.validator == "additionalProperties":
            return "Additional properties not allowed"

        # Default to jsonschema message
        return error.message

    def _check_semantics(self, config: dict[str, Any]) -> list[str]:
        """
        Perform semantic validation beyond schema

        Args:
            config: Configuration dict

        Returns:
            List of warning messages
        """
        warnings: list[str] = []

        # Check for common misconfigurations
        if "model" in config:
            model = config["model"]
            if isinstance(model, str) and "/" not in model:
                warnings.append(
                    f"Model '{model}' should include provider prefix (e.g., 'anthropic/{model}')"
                )

        # Check permission settings
        if "permission" in config:
            perms = config["permission"]
            if "*" not in perms:
                warnings.append(
                    "No default permission ('*') set; "
                    "tools without explicit permission will use system default"
                )

        # Check provider API keys
        if "provider" in config:
            for provider, settings in config["provider"].items():
                if isinstance(settings, dict):
                    api_key = settings.get("apiKey", "")
                    if api_key and not api_key.startswith("{env:"):
                        warnings.append(
                            f"Provider '{provider}' has hardcoded API key; "
                            f"consider using {{env:VAR}} for security"
                        )

        return warnings

    def validate_file(self, config_path: Path) -> ValidationResult:
        """
        Validate a configuration file

        Args:
            config_path: Path to config file

        Returns:
            ValidationResult with validation status
        """
        try:
            with open(config_path, encoding="utf-8") as f:
                content = f.read()
        except FileNotFoundError:
            return ValidationResult(
                valid=False,
                errors=[
                    ValidationErrorInfo(
                        path="",
                        message=f"Config file not found: {config_path}",
                    )
                ],
            )
        except Exception as e:
            return ValidationResult(
                valid=False,
                errors=[
                    ValidationErrorInfo(
                        path="",
                        message=f"Error reading config file: {e}",
                    )
                ],
            )

        # Strip JSONC comments
        from groknroll.config.loader import ConfigLoader

        loader = ConfigLoader()
        try:
            json_content = loader._strip_comments(content)
            config = json.loads(json_content)
        except json.JSONDecodeError as e:
            return ValidationResult(
                valid=False,
                errors=[
                    ValidationErrorInfo(
                        path="",
                        message=f"Invalid JSON at line {e.lineno}: {e.msg}",
                    )
                ],
            )

        if not isinstance(config, dict):
            return ValidationResult(
                valid=False,
                errors=[
                    ValidationErrorInfo(
                        path="",
                        message="Config must be a JSON object",
                    )
                ],
            )

        return self.validate(config)

    def get_schema(self) -> dict[str, Any]:
        """
        Get the validation schema

        Returns:
            The JSON schema dict
        """
        return self.schema.copy()

    def get_schema_url(self) -> str:
        """
        Get the schema URL for $schema field

        Returns:
            URL to use in config $schema field
        """
        return self.schema.get("$id", "https://groknroll.dev/config.json")


# Convenience functions


def validate_config(
    config: dict[str, Any],
    strict: bool = False,
) -> ValidationResult:
    """
    Validate configuration (convenience function)

    Args:
        config: Configuration dict to validate
        strict: If True, treat warnings as errors

    Returns:
        ValidationResult with validation status
    """
    validator = SchemaValidator(strict=strict)
    return validator.validate(config)


def validate_config_file(
    config_path: Path,
    strict: bool = False,
) -> ValidationResult:
    """
    Validate configuration file (convenience function)

    Args:
        config_path: Path to config file
        strict: If True, treat warnings as errors

    Returns:
        ValidationResult with validation status
    """
    validator = SchemaValidator(strict=strict)
    return validator.validate_file(config_path)


def is_valid_config(config: dict[str, Any]) -> bool:
    """
    Quick check if config is valid (convenience function)

    Args:
        config: Configuration dict to validate

    Returns:
        True if valid, False otherwise
    """
    result = validate_config(config)
    return result.valid
