"""
Manifest validation for automations.

This module provides validation for automation manifest files,
ensuring they meet all requirements for submission.
"""

from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any

import yaml
from pydantic import ValidationError

from torivers_sdk.automation.metadata import (
    AutomationMetadata,
    AutomationType,
    ScheduleConfig,
    WebhookConfig,
)


class ValidationLevel(str, Enum):
    """Severity level for validation issues."""

    ERROR = "error"
    WARNING = "warning"
    INFO = "info"


@dataclass
class ValidationIssue:
    """A single validation issue."""

    level: ValidationLevel
    code: str
    message: str
    location: str | None = None

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


@dataclass
class ValidationResult:
    """Result of manifest validation."""

    valid: bool
    issues: list[ValidationIssue] = field(default_factory=list)
    metadata: AutomationMetadata | None = None

    @property
    def errors(self) -> list[ValidationIssue]:
        """Get only error-level issues."""
        return [i for i in self.issues if i.level == ValidationLevel.ERROR]

    @property
    def warnings(self) -> list[ValidationIssue]:
        """Get only warning-level issues."""
        return [i for i in self.issues if i.level == ValidationLevel.WARNING]

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "valid": self.valid,
            "issues": [i.to_dict() for i in self.issues],
            "error_count": len(self.errors),
            "warning_count": len(self.warnings),
        }


class ManifestValidator:
    """
    Validator for automation manifest files.

    This validator checks automation.yaml files for required fields,
    valid values, and best practices.

    Example:
        validator = ManifestValidator()
        result = validator.validate("automation.yaml")

        if not result.valid:
            for error in result.errors:
                print(f"Error: {error.message}")

    Or validate a directory:
        result = validator.validate("/path/to/automation")
    """

    # Required fields per the automation spec
    REQUIRED_FIELDS = [
        "name",
        "version",
        "title",
        "description",
        "kind",
        "input_schema",
        "output_schema",
        "base_price",
    ]

    REQUIRED_FILES = ["automation.yaml", "automation.py"]

    ALLOWED_CATEGORIES = [
        "general",
        "productivity",
        "marketing",
        "communication",
        "data-processing",
        "finance",
        "development",
        "analytics",
        "content-creation",
        "research",
        "customer-support",
    ]

    # Automation kinds (what the automation does)
    ALLOWED_KINDS = ["workflow", "tool", "agent"]

    def validate_file(self, path: str | Path) -> ValidationResult:
        """
        Validate a manifest file.

        Args:
            path: Path to automation.yaml

        Returns:
            Validation result with issues
        """
        path = Path(path)
        issues: list[ValidationIssue] = []

        # Check file exists
        if not path.exists():
            return ValidationResult(
                valid=False,
                issues=[
                    ValidationIssue(
                        level=ValidationLevel.ERROR,
                        code="E001",
                        message=f"Manifest file not found: {path}",
                        location=str(path),
                    )
                ],
            )

        # Parse YAML
        try:
            with open(path) as f:
                data = yaml.safe_load(f)
        except yaml.YAMLError as e:
            return ValidationResult(
                valid=False,
                issues=[
                    ValidationIssue(
                        level=ValidationLevel.ERROR,
                        code="E002",
                        message=f"Invalid YAML: {e}",
                        location=str(path),
                    )
                ],
            )

        # Validate content
        return self.validate_dict(data, issues)

    def validate_dict(
        self,
        data: dict[str, Any],
        issues: list[ValidationIssue] | None = None,
    ) -> ValidationResult:
        """
        Validate a manifest dictionary.

        Args:
            data: Manifest data as dictionary
            issues: Optional list to append issues to

        Returns:
            Validation result with issues
        """
        issues = issues or []

        if not isinstance(data, dict):
            return ValidationResult(
                valid=False,
                issues=[
                    ValidationIssue(
                        level=ValidationLevel.ERROR,
                        code="E003",
                        message="Manifest must be a dictionary",
                    )
                ],
            )

        # Validate using Pydantic model
        try:
            metadata = AutomationMetadata(**data)
        except ValidationError as e:
            for error in e.errors():
                loc = ".".join(str(x) for x in error["loc"])
                issues.append(
                    ValidationIssue(
                        level=ValidationLevel.ERROR,
                        code="E100",
                        message=error["msg"],
                        location=loc,
                    )
                )
            return ValidationResult(valid=False, issues=issues)

        # Additional validations
        self._validate_category(metadata, issues)
        self._validate_credentials(metadata, issues)
        self._validate_pricing(metadata, issues)
        self._validate_schedule_config(metadata, issues)
        self._validate_webhook_config(metadata, issues)

        valid = len([i for i in issues if i.level == ValidationLevel.ERROR]) == 0

        return ValidationResult(valid=valid, issues=issues, metadata=metadata)

    def _validate_category(
        self,
        metadata: AutomationMetadata,
        issues: list[ValidationIssue],
    ) -> None:
        """Validate category is in allowed list."""
        if metadata.category not in self.ALLOWED_CATEGORIES:
            issues.append(
                ValidationIssue(
                    level=ValidationLevel.WARNING,
                    code="W001",
                    message=(
                        f"Category '{metadata.category}' is not in the standard list. "
                        f"Consider using one of: {', '.join(self.ALLOWED_CATEGORIES)}"
                    ),
                    location="category",
                )
            )

    def _validate_credentials(
        self,
        metadata: AutomationMetadata,
        issues: list[ValidationIssue],
    ) -> None:
        """Validate credential declarations."""
        all_creds = metadata.required_credentials + metadata.optional_credentials

        if len(all_creds) > 10:
            issues.append(
                ValidationIssue(
                    level=ValidationLevel.WARNING,
                    code="W002",
                    message="Automation requires many credentials. Consider simplifying.",
                    location="credentials",
                )
            )

    def _validate_pricing(
        self,
        metadata: AutomationMetadata,
        issues: list[ValidationIssue],
    ) -> None:
        """Validate pricing configuration."""
        if metadata.base_price == 0:
            issues.append(
                ValidationIssue(
                    level=ValidationLevel.INFO,
                    code="I001",
                    message="Automation is free. Consider setting a price.",
                    location="base_price",
                )
            )

    def _validate_schedule_config(
        self,
        metadata: AutomationMetadata,
        issues: list[ValidationIssue],
    ) -> None:
        """Validate schedule configuration for scheduled automations."""
        if metadata.automation_type != AutomationType.SCHEDULED:
            return

        if not metadata.schedule_config:
            issues.append(
                ValidationIssue(
                    level=ValidationLevel.ERROR,
                    code="E200",
                    message="schedule_config is required for scheduled automation type",
                    location="schedule_config",
                )
            )
            return

        # Schedule config is already validated by Pydantic when converted to ScheduleConfig
        # Add informational messages for complex schedules
        if isinstance(metadata.schedule_config, ScheduleConfig):
            config = metadata.schedule_config
            # Warn about very frequent schedules
            cron = config.cron
            if cron.startswith("*"):
                issues.append(
                    ValidationIssue(
                        level=ValidationLevel.WARNING,
                        code="W200",
                        message=(
                            f"Schedule '{cron}' runs very frequently. "
                            "Consider if this frequency is necessary."
                        ),
                        location="schedule_config.cron",
                    )
                )

            # Info about timezone
            if config.timezone != "UTC":
                issues.append(
                    ValidationIssue(
                        level=ValidationLevel.INFO,
                        code="I200",
                        message=f"Schedule uses timezone '{config.timezone}'",
                        location="schedule_config.timezone",
                    )
                )

    def _validate_webhook_config(
        self,
        metadata: AutomationMetadata,
        issues: list[ValidationIssue],
    ) -> None:
        """Validate webhook configuration for webhook automations."""
        if metadata.automation_type != AutomationType.WEBHOOK:
            return

        # Webhook config is optional but recommended to be explicit
        if not metadata.webhook_config:
            # Default config will be applied, add info
            issues.append(
                ValidationIssue(
                    level=ValidationLevel.INFO,
                    code="I210",
                    message="Using default webhook config (POST, application/json, signature auth)",
                    location="webhook_config",
                )
            )
            return

        if isinstance(metadata.webhook_config, WebhookConfig):
            config = metadata.webhook_config

            # Warn about no authentication
            if config.authentication == "none":
                issues.append(
                    ValidationIssue(
                        level=ValidationLevel.WARNING,
                        code="W210",
                        message=(
                            "Webhook authentication is disabled. "
                            "Consider using 'signature' or 'api_key' for security."
                        ),
                        location="webhook_config.authentication",
                    )
                )

            # Info about sync mode
            if config.response_mode == "sync":
                issues.append(
                    ValidationIssue(
                        level=ValidationLevel.INFO,
                        code="I211",
                        message=(
                            "Webhook uses synchronous mode. "
                            "Requests will wait for execution to complete (max 25s)."
                        ),
                        location="webhook_config.response_mode",
                    )
                )

    def validate(self, manifest_path: str | Path) -> ValidationResult:
        """
        Validate a manifest file against schema requirements.

        This is the primary entry point for manifest validation.

        Args:
            manifest_path: Path to automation.yaml or directory containing it

        Returns:
            ValidationResult with issues and metadata
        """
        path = Path(manifest_path)

        # If directory, look for automation.yaml
        if path.is_dir():
            manifest_file = path / "automation.yaml"
            path = manifest_file

        return self.validate_file(path)

    def validate_input_schema(self, schema: dict[str, Any]) -> list[str]:
        """
        Validate that an input schema is a valid JSON Schema.

        Args:
            schema: JSON Schema definition for automation input

        Returns:
            List of error messages (empty if valid)

        Example:
            errors = validator.validate_input_schema({
                "type": "object",
                "properties": {
                    "query": {"type": "string"}
                },
                "required": ["query"]
            })
        """
        return self._validate_json_schema(schema, "input_schema")

    def validate_output_schema(self, schema: dict[str, Any]) -> list[str]:
        """
        Validate that an output schema is a valid JSON Schema.

        Args:
            schema: JSON Schema definition for automation output

        Returns:
            List of error messages (empty if valid)

        Example:
            errors = validator.validate_output_schema({
                "type": "object",
                "properties": {
                    "result": {"type": "string"},
                    "items": {"type": "array"}
                }
            })
        """
        return self._validate_json_schema(schema, "output_schema")

    def _validate_json_schema(
        self,
        schema: dict[str, Any],
        schema_name: str,
    ) -> list[str]:
        """
        Validate a JSON Schema definition.

        Args:
            schema: The schema to validate
            schema_name: Name of schema for error messages

        Returns:
            List of error messages
        """
        errors: list[str] = []

        if not isinstance(schema, dict):
            errors.append(f"{schema_name} must be an object")
            return errors

        # Check for type property
        if "type" not in schema:
            errors.append(f"{schema_name} should specify a 'type' property")

        schema_type = schema.get("type")

        # Validate object schemas
        if schema_type == "object":
            errors.extend(self._validate_object_schema(schema, schema_name))

        # Validate array schemas
        elif schema_type == "array":
            errors.extend(self._validate_array_schema(schema, schema_name))

        return errors

    def _validate_object_schema(
        self,
        schema: dict[str, Any],
        path: str,
    ) -> list[str]:
        """Validate an object type JSON Schema."""
        errors: list[str] = []

        properties = schema.get("properties", {})
        required = schema.get("required", [])

        if not isinstance(properties, dict):
            errors.append(f"{path}.properties must be an object")
            return errors

        # Check required fields exist in properties
        for req_field in required:
            if req_field not in properties:
                errors.append(
                    f"{path}.required contains '{req_field}' "
                    f"but it's not defined in properties"
                )

        # Validate nested schemas
        for prop_name, prop_schema in properties.items():
            if not isinstance(prop_schema, dict):
                errors.append(f"{path}.properties.{prop_name} must be an object")
                continue

            prop_type = prop_schema.get("type")
            if prop_type == "object":
                errors.extend(
                    self._validate_object_schema(
                        prop_schema, f"{path}.properties.{prop_name}"
                    )
                )
            elif prop_type == "array":
                errors.extend(
                    self._validate_array_schema(
                        prop_schema, f"{path}.properties.{prop_name}"
                    )
                )

        return errors

    def _validate_array_schema(
        self,
        schema: dict[str, Any],
        path: str,
    ) -> list[str]:
        """Validate an array type JSON Schema."""
        errors: list[str] = []

        items = schema.get("items")

        # Arrays should define items schema
        if items is None:
            errors.append(f"{path} is an array but does not define 'items' schema")
        elif isinstance(items, dict):
            items_type = items.get("type")
            if items_type == "object":
                errors.extend(self._validate_object_schema(items, f"{path}.items"))
            elif items_type == "array":
                errors.extend(self._validate_array_schema(items, f"{path}.items"))

        return errors
