"""
Script Definition Models - Pydantic schemas for automation scripts.

Derived from: definitions/automation/scripts/schema.yaml

This module provides typed models for script definitions, enabling:
- Validation of script registry entries
- CLI command generation
- Script discovery and documentation

Usage:
    from lightwave.schema.pydantic.automation import ScriptDefinition, ScriptRegistry

    # Load and validate a script definition
    script = ScriptDefinition(
        script_id="sst_create_agent",
        name="Create Agent",
        description="Creates a new agent definition",
        domain=ScriptDomain.SST,
        operation=ScriptOperation.CREATE,
        target="agent",
        implementation=ScriptImplementation(
            language="python",
            path="scripts/sst/create/agent.py",
        ),
    )

    # Load entire registry
    registry = ScriptRegistry.from_yaml()
    scripts = registry.get_by_domain(ScriptDomain.SST)
"""

from __future__ import annotations

from enum import Enum
from pathlib import Path
from typing import Any

from pydantic import Field, field_validator, model_validator

from lightwave.schema.pydantic.core.base import LightwaveBaseSchema


class ScriptOperation(str, Enum):
    """
    Valid script operation types.

    Derived from: definitions/automation/scripts/schema.yaml#operations
    """

    # Create operations
    CREATE = "create"

    # Read operations
    READ = "read"
    LIST = "list"
    SHOW = "show"

    # Update operations
    UPDATE = "update"
    MIGRATE = "migrate"
    SYNC = "sync"

    # Delete operations
    DELETE = "delete"
    ARCHIVE = "archive"
    CLEANUP = "cleanup"

    # Validation operations
    VALIDATE = "validate"
    AUDIT = "audit"
    CHECK = "check"

    # Generation operations
    GENERATE = "generate"
    SCAFFOLD = "scaffold"

    # Execution operations
    RUN = "run"
    EXECUTE = "execute"

    @property
    def is_destructive(self) -> bool:
        """Check if operation can cause data loss."""
        return self in {self.DELETE, self.CLEANUP}

    @property
    def is_idempotent(self) -> bool:
        """Check if operation is safe to run multiple times."""
        return self not in {self.CREATE, self.SCAFFOLD, self.RUN, self.EXECUTE}


class ScriptDomain(str, Enum):
    """
    Script domain categories.

    Derived from: definitions/automation/scripts/schema.yaml#domains
    """

    SST = "sst"
    CI = "ci"
    DB = "db"
    SECURITY = "security"
    DEV = "dev"
    INFRA = "infra"
    TEST = "test"
    CONTENT = "content"
    AI = "ai"

    @property
    def path_prefix(self) -> str:
        """Get the directory prefix for this domain."""
        return f"scripts/{self.value}"


class ImplementationLanguage(str, Enum):
    """Supported implementation languages."""

    PYTHON = "python"
    BASH = "bash"
    TYPESCRIPT = "typescript"
    GO = "go"


class ScriptImplementation(LightwaveBaseSchema):
    """Script implementation details."""

    language: ImplementationLanguage = Field(description="Implementation language")
    path: str = Field(description="Relative path from repo root")
    entrypoint: str | None = Field(default=None, description="Main function/command (for Python modules)")

    @field_validator("path")
    @classmethod
    def validate_path(cls, v: str) -> str:
        """Ensure path follows naming convention."""
        if not v.startswith("scripts/"):
            raise ValueError("Script path must start with 'scripts/'")
        return v


class CLIArgument(LightwaveBaseSchema):
    """CLI positional argument definition."""

    name: str = Field(description="Argument name")
    type: str = Field(description="Argument type (string, integer, boolean)")
    required: bool = Field(default=False, description="Whether argument is required")
    description: str = Field(description="Argument description")


class CLIFlag(LightwaveBaseSchema):
    """CLI flag definition."""

    name: str = Field(description="Flag name (without --)")
    short: str | None = Field(default=None, description="Short flag (single char)")
    type: str = Field(description="Flag type (string, integer, boolean)")
    default: Any = Field(default=None, description="Default value")
    description: str = Field(description="Flag description")

    @field_validator("short")
    @classmethod
    def validate_short(cls, v: str | None) -> str | None:
        """Ensure short flag is single character."""
        if v is not None and len(v) != 1:
            raise ValueError("Short flag must be a single character")
        return v


class ScriptCLI(LightwaveBaseSchema):
    """CLI integration configuration."""

    command: str = Field(description="Full CLI command (e.g., 'lw sst create agent')")
    aliases: list[str] = Field(default_factory=list, description="Alternative command names")
    arguments: list[CLIArgument] = Field(default_factory=list, description="Positional arguments")
    flags: list[CLIFlag] = Field(default_factory=list, description="Command flags")

    @field_validator("command")
    @classmethod
    def validate_command(cls, v: str) -> str:
        """Ensure command starts with 'lw'."""
        if not v.startswith("lw "):
            raise ValueError("CLI command must start with 'lw '")
        return v

    def get_usage(self) -> str:
        """Generate usage string."""
        parts = [self.command]

        for arg in self.arguments:
            if arg.required:
                parts.append(f"<{arg.name}>")
            else:
                parts.append(f"[{arg.name}]")

        return " ".join(parts)


class ScriptBehavior(LightwaveBaseSchema):
    """Script behavior characteristics."""

    idempotent: bool = Field(default=False, description="Safe to run multiple times")
    destructive: bool = Field(default=False, description="Can cause data loss")
    requires_confirmation: bool = Field(default=False, description="Requires user confirmation before execution")
    dry_run_supported: bool = Field(default=True, description="Supports --dry-run flag")
    json_output_supported: bool = Field(default=True, description="Supports --json flag for machine-readable output")

    @model_validator(mode="after")
    def validate_destructive_requires_confirmation(self) -> "ScriptBehavior":
        """Destructive operations should require confirmation."""
        if self.destructive and not self.requires_confirmation:
            # Auto-enable confirmation for destructive ops
            object.__setattr__(self, "requires_confirmation", True)
        return self


class ScriptDependencies(LightwaveBaseSchema):
    """Script dependencies."""

    scripts: list[str] = Field(default_factory=list, description="Other scripts this depends on")
    packages: list[str] = Field(default_factory=list, description="Python/npm packages required")
    services: list[str] = Field(default_factory=list, description="Services that must be running")


class ScriptDefinition(LightwaveBaseSchema):
    """
    Complete script definition.

    This is the Pydantic model for entries in the script registry.
    """

    # Identity
    script_id: str = Field(description="Unique script identifier", pattern=r"^[a-z][a-z0-9_]*$")
    name: str = Field(description="Human-readable script name")
    description: str = Field(description="What the script does", max_length=500)

    # Classification
    domain: ScriptDomain = Field(description="Script domain category")
    operation: ScriptOperation = Field(description="CRUD operation type")
    target: str = Field(description="What resource the script operates on")

    # Implementation
    implementation: ScriptImplementation = Field(description="Implementation details")

    # CLI Integration
    cli: ScriptCLI | None = Field(default=None, description="CLI command configuration")

    # Contracts
    input_schema: str | None = Field(default=None, description="Reference to Pydantic schema for input validation")
    output_schema: str | None = Field(default=None, description="Reference to Pydantic schema for output format")

    # Behavior
    behavior: ScriptBehavior = Field(default_factory=ScriptBehavior, description="Script behavior characteristics")

    # Dependencies
    dependencies: ScriptDependencies | None = Field(default=None, description="Script dependencies")

    # Metadata
    maintainer: str | None = Field(default=None, description="Team or person responsible")
    version: str | None = Field(default=None, pattern=r"^\d+\.\d+\.\d+$", description="Script version")
    deprecated: bool = Field(default=False, description="Whether script is deprecated")
    replacement: str | None = Field(default=None, description="Script ID that replaces this one if deprecated")

    @model_validator(mode="after")
    def validate_script_id_matches_classification(self) -> "ScriptDefinition":
        """Ensure script_id follows naming convention."""
        expected_prefix = f"{self.domain.value}_"
        if not self.script_id.startswith(expected_prefix):
            # Allow ai_ prefix for ai domain scripts that use 'agent' commands
            if not (self.domain == ScriptDomain.AI and self.script_id.startswith("ai_")):
                raise ValueError(
                    f"Script ID '{self.script_id}' should start with '{expected_prefix}' "
                    f"for domain '{self.domain.value}'"
                )
        return self

    @property
    def expected_path(self) -> str:
        """Generate expected implementation path."""
        return f"scripts/{self.domain.value}/{self.operation.value}/{self.target}.py"

    def implementation_exists(self, repo_root: Path | None = None) -> bool:
        """Check if implementation file exists."""
        if repo_root is None:
            repo_root = Path.cwd()
        return (repo_root / self.implementation.path).exists()


class ScriptRegistry(LightwaveBaseSchema):
    """
    Registry of all automation scripts.

    Provides methods to load, query, and validate script definitions.
    """

    scripts: dict[str, ScriptDefinition] = Field(default_factory=dict, description="Map of script_id to definition")

    @classmethod
    def from_yaml(cls, path: Path | None = None) -> "ScriptRegistry":
        """Load registry from YAML file."""
        import yaml

        if path is None:
            # Default path relative to lightwave-core
            path = Path(__file__).parent.parent.parent / "definitions" / "automation" / "scripts" / "registry.yaml"

        with open(path) as f:
            data = yaml.safe_load(f)

        scripts = {}
        for script_id, script_data in data.get("scripts", {}).items():
            script_data["script_id"] = script_id
            scripts[script_id] = ScriptDefinition(**script_data)

        return cls(scripts=scripts)

    def get(self, script_id: str) -> ScriptDefinition | None:
        """Get a script by ID."""
        return self.scripts.get(script_id)

    def get_by_domain(self, domain: ScriptDomain) -> list[ScriptDefinition]:
        """Get all scripts in a domain."""
        return [s for s in self.scripts.values() if s.domain == domain]

    def get_by_operation(self, operation: ScriptOperation) -> list[ScriptDefinition]:
        """Get all scripts with a specific operation."""
        return [s for s in self.scripts.values() if s.operation == operation]

    def get_cli_commands(self) -> dict[str, ScriptDefinition]:
        """Get map of CLI commands to scripts."""
        result = {}
        for script in self.scripts.values():
            if script.cli:
                result[script.cli.command] = script
                for alias in script.cli.aliases:
                    result[alias] = script
        return result

    def validate_implementations(self, repo_root: Path | None = None) -> list[str]:
        """
        Check that all script implementations exist.

        Returns list of missing implementation paths.
        """
        missing = []
        for script in self.scripts.values():
            if not script.implementation_exists(repo_root):
                missing.append(script.implementation.path)
        return missing


# =============================================================================
# Exit Codes
# =============================================================================


class ExitCode(int, Enum):
    """
    Standard exit codes for all scripts.

    Derived from: definitions/automation/scripts/schema.yaml#exit_codes
    """

    SUCCESS = 0
    VALIDATION_ERROR = 1
    RUNTIME_ERROR = 2
    DEPENDENCY_ERROR = 3
    PERMISSION_ERROR = 4
    NOT_FOUND = 5
    USER_CANCELLED = 10
    NOT_EXECUTABLE = 126
    SCRIPT_NOT_FOUND = 127
