"""
Automation metadata for marketplace listing and validation.

This module provides the AutomationMetadata class that defines
all the information needed to list an automation in the marketplace.
"""

import re
from enum import Enum
from pathlib import Path
from typing import Any, Literal

import yaml
from croniter import croniter
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator


class AutomationType(str, Enum):
    """Type of automation execution trigger."""

    STANDARD = "standard"
    WEBHOOK = "webhook"
    SCHEDULED = "scheduled"


class AutomationKind(str, Enum):
    """Kind of automation (what it does, not how it's triggered)."""

    WORKFLOW = "workflow"
    TOOL = "tool"
    AGENT = "agent"


class ExecutionLimits(BaseModel):
    """Resource limits for automation execution."""

    max_duration_seconds: int = Field(
        default=300,
        ge=1,
        le=3600,
        description="Maximum execution time in seconds",
    )
    max_memory_mb: int = Field(
        default=512,
        ge=128,
        le=4096,
        description="Maximum memory usage in MB",
    )
    max_output_size_bytes: int = Field(
        default=10 * 1024 * 1024,  # 10MB
        ge=1024,
        description="Maximum output size in bytes",
    )
    max_file_count: int = Field(
        default=100,
        ge=1,
        description="Maximum number of output files",
    )
    max_llm_calls: int = Field(
        default=50,
        ge=1,
        description="Maximum number of LLM API calls",
    )
    max_http_requests: int = Field(
        default=100,
        ge=1,
        description="Maximum number of HTTP requests",
    )


class ScheduleConfig(BaseModel):
    """Configuration for scheduled automation execution."""

    cron: str = Field(
        ...,
        description="Cron expression for schedule (e.g., '0 9 * * MON-FRI' for 9 AM weekdays)",
    )
    timezone: str = Field(
        default="UTC",
        description="Timezone for cron expression (e.g., 'America/New_York')",
    )
    enabled: bool = Field(
        default=True,
        description="Whether the schedule is enabled",
    )
    retry_on_failure: bool = Field(
        default=False,
        description="Retry execution if it fails",
    )
    max_retries: int = Field(
        default=3,
        ge=0,
        le=10,
        description="Maximum number of retries on failure",
    )

    @field_validator("cron")
    @classmethod
    def validate_cron_expression(cls, v: str) -> str:
        """Validate that the cron expression is syntactically correct."""
        if not croniter.is_valid(v):
            raise ValueError(
                f"Invalid cron expression: '{v}'. "
                "Expected format: 'minute hour day month weekday' "
                "(e.g., '0 9 * * MON-FRI' for 9 AM weekdays)"
            )
        return v


class WebhookConfig(BaseModel):
    """Configuration for webhook-triggered automation execution."""

    methods: list[Literal["POST", "GET", "PUT", "PATCH", "DELETE"]] = Field(
        default=["POST"],
        description="Allowed HTTP methods for the webhook",
    )
    content_types: list[str] = Field(
        default=["application/json"],
        description="Allowed content types for webhook requests",
    )
    authentication: Literal["signature", "api_key", "none"] = Field(
        default="signature",
        description="Authentication method for webhook requests",
    )
    response_mode: Literal["sync", "async"] = Field(
        default="async",
        description="Response mode: sync waits for execution, async returns immediately",
    )
    path: str | None = Field(
        default=None,
        description="Custom path suffix for the webhook URL (optional)",
    )
    rate_limit: int | None = Field(
        default=None,
        ge=1,
        description="Rate limit (requests per minute)",
    )
    session_config: dict[str, Any] | None = Field(
        default=None,
        description="Session configuration for multi-turn conversations",
    )


class InputField(BaseModel):
    """Definition of an input field for the automation."""

    name: str = Field(..., min_length=1, max_length=50)
    type: str = Field(..., pattern=r"^(string|number|boolean|array|object)$")
    description: str = Field(..., min_length=1, max_length=500)
    required: bool = Field(default=True)
    default: Any = Field(default=None)


class OutputField(BaseModel):
    """Definition of an output field for the automation."""

    name: str = Field(..., min_length=1, max_length=50)
    type: str = Field(..., pattern=r"^(string|number|boolean|array|object)$")
    description: str = Field(..., min_length=1, max_length=500)


class AutomationMetadata(BaseModel):
    """
    Metadata for an automation that appears in the marketplace.

    This metadata is used for:
    - Marketplace listing display
    - Search and categorization
    - Validation during submission
    - Runtime configuration

    Example:
        metadata = AutomationMetadata(
            name="email-summarizer",
            title="Email Summarizer",
            version="1.0.0",
            description="Summarizes emails using AI",
            category="productivity",
            tags=["email", "ai", "summary"],
            required_credentials=["gmail"],
            automation_type=AutomationType.STANDARD,
            input_schema={
                "type": "object",
                "properties": {
                    "email_count": {"type": "integer", "default": 10}
                }
            },
        )

        # Load from YAML manifest
        metadata = AutomationMetadata.from_yaml("automation.yaml")
    """

    model_config = ConfigDict(extra="forbid")

    # Required fields
    name: str = Field(
        ...,
        min_length=3,
        max_length=50,
        description="Unique automation name (lowercase, hyphens only)",
    )
    title: str = Field(
        ...,
        min_length=3,
        max_length=100,
        description="Human-readable title for marketplace",
    )
    version: str = Field(
        ...,
        description="Semantic version (e.g., 1.0.0)",
    )
    description: str = Field(
        ...,
        min_length=10,
        max_length=500,
        description="Short description for marketplace listing",
    )

    # Automation type (execution trigger)
    automation_type: AutomationType = Field(
        default=AutomationType.STANDARD,
        description="Execution trigger type: standard, webhook, or scheduled",
    )

    # Automation kind (what the automation does)
    kind: AutomationKind = Field(
        default=AutomationKind.WORKFLOW,
        description="Automation kind: workflow, tool, or agent",
    )

    # Optional fields
    detailed_description: str | None = Field(
        default=None,
        max_length=5000,
        description="Long-form description with markdown support",
    )
    category: str = Field(
        default="productivity",
        description="Primary category for marketplace filtering",
    )
    tags: list[str] = Field(
        default_factory=list,
        max_length=10,
        description="Tags for search and discovery",
    )

    # Credential requirements
    required_credentials: list[str] = Field(
        default_factory=list,
        description="Services that must be connected before execution",
    )
    optional_credentials: list[str] = Field(
        default_factory=list,
        description="Services that enhance functionality if connected",
    )

    # Schema definitions
    input_schema: dict[str, Any] = Field(
        default_factory=lambda: {"type": "object", "properties": {}},
        description="JSON Schema for input validation",
    )
    output_schema: dict[str, Any] = Field(
        default_factory=lambda: {"type": "object", "properties": {}},
        description="JSON Schema describing output structure",
    )

    # Pricing
    base_price: float = Field(
        ...,
        ge=0.0,
        description="Base price in credits per execution",
    )

    # Execution limits
    limits: ExecutionLimits = Field(
        default_factory=ExecutionLimits,
        description="Resource limits for execution",
    )

    # Execution configuration
    estimated_duration_seconds: int = Field(
        default=60,
        ge=1,
        le=3600,
        description="Estimated execution time in seconds",
    )

    # Author information
    author_name: str | None = Field(
        default=None,
        max_length=100,
        description="Display name of the author",
    )
    author_url: str | None = Field(
        default=None,
        description="Author's website or profile URL",
    )
    repository_url: str | None = Field(
        default=None,
        description="Source code repository URL",
    )
    documentation_url: str | None = Field(
        default=None,
        description="Documentation URL",
    )

    # Additional metadata
    extra_metadata: dict[str, Any] = Field(
        default_factory=dict,
        description="Additional custom metadata",
    )

    # Webhook configuration (only for webhook type)
    webhook_config: WebhookConfig | dict[str, Any] | None = Field(
        default=None,
        description="Webhook-specific configuration",
    )

    # Schedule configuration (only for scheduled type)
    schedule_config: ScheduleConfig | dict[str, Any] | None = Field(
        default=None,
        description="Schedule-specific configuration (cron expression, timezone, etc.)",
    )

    @field_validator("name")
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Ensure name follows naming convention."""
        if not re.match(r"^[a-z][a-z0-9-]*[a-z0-9]$", v):
            raise ValueError(
                "Name must be lowercase, start with a letter, "
                "end with a letter or number, and use only hyphens as separators"
            )
        return v

    @field_validator("version")
    @classmethod
    def validate_version(cls, v: str) -> str:
        """Ensure version follows semver format."""
        if not re.match(r"^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$", v):
            raise ValueError(
                "Version must follow semantic versioning (e.g., 1.0.0 or 1.0.0-beta.1)"
            )
        return v

    @field_validator("tags")
    @classmethod
    def validate_tags(cls, v: list[str]) -> list[str]:
        """Ensure tags are valid."""
        validated = []
        for tag in v:
            tag = tag.lower().strip()
            if not re.match(r"^[a-z][a-z0-9-]*$", tag):
                raise ValueError(f"Invalid tag: {tag}. Tags must be lowercase.")
            if len(tag) > 30:
                raise ValueError(f"Tag too long: {tag}. Max 30 characters.")
            validated.append(tag)
        return validated

    @model_validator(mode="after")
    def validate_credentials(self) -> "AutomationMetadata":
        """Ensure no overlap between required and optional credentials."""
        overlap = set(self.required_credentials) & set(self.optional_credentials)
        if overlap:
            raise ValueError(
                f"Credentials cannot be both required and optional: {overlap}"
            )
        return self

    @model_validator(mode="after")
    def validate_type_config(self) -> "AutomationMetadata":
        """Ensure type-specific configuration is provided and validated."""
        # Validate and convert webhook config
        if self.automation_type == AutomationType.WEBHOOK:
            if not self.webhook_config:
                # Set default webhook config
                self.webhook_config = WebhookConfig()
            elif isinstance(self.webhook_config, dict):
                # Convert dict to WebhookConfig for validation
                self.webhook_config = WebhookConfig(**self.webhook_config)

        # Validate and convert schedule config
        if self.automation_type == AutomationType.SCHEDULED:
            if not self.schedule_config:
                raise ValueError(
                    "schedule_config is required for scheduled automation type"
                )
            if isinstance(self.schedule_config, dict):
                # Convert dict to ScheduleConfig for validation (includes cron validation)
                self.schedule_config = ScheduleConfig(**self.schedule_config)

        return self

    @classmethod
    def from_yaml(cls, path: str | Path) -> "AutomationMetadata":
        """
        Load automation metadata from a YAML manifest file.

        Args:
            path: Path to the automation.yaml file

        Returns:
            AutomationMetadata instance

        Raises:
            FileNotFoundError: If the file doesn't exist
            ValueError: If the YAML is invalid
        """
        path = Path(path)

        if not path.exists():
            raise FileNotFoundError(f"Manifest file not found: {path}")

        with open(path, "r", encoding="utf-8") as f:
            data = yaml.safe_load(f)

        if not isinstance(data, dict):
            raise ValueError("Manifest must be a YAML dictionary")

        # Handle nested structures
        if "limits" in data and isinstance(data["limits"], dict):
            data["limits"] = ExecutionLimits(**data["limits"])

        # Handle schedule_config
        if "schedule_config" in data and isinstance(data["schedule_config"], dict):
            data["schedule_config"] = ScheduleConfig(**data["schedule_config"])

        # Handle webhook_config
        if "webhook_config" in data and isinstance(data["webhook_config"], dict):
            data["webhook_config"] = WebhookConfig(**data["webhook_config"])

        # Handle automation_type from string
        if "automation_type" in data and isinstance(data["automation_type"], str):
            data["automation_type"] = AutomationType(data["automation_type"])

        # Handle kind from string
        if "kind" in data and isinstance(data["kind"], str):
            data["kind"] = AutomationKind(data["kind"])

        return cls(**data)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "AutomationMetadata":
        """
        Create metadata from a dictionary.

        Args:
            data: Dictionary with metadata fields

        Returns:
            AutomationMetadata instance
        """
        data = data.copy()

        # Handle nested structures
        if "limits" in data and isinstance(data["limits"], dict):
            data["limits"] = ExecutionLimits(**data["limits"])

        # Handle schedule_config
        if "schedule_config" in data and isinstance(data["schedule_config"], dict):
            data["schedule_config"] = ScheduleConfig(**data["schedule_config"])

        # Handle webhook_config
        if "webhook_config" in data and isinstance(data["webhook_config"], dict):
            data["webhook_config"] = WebhookConfig(**data["webhook_config"])

        # Handle automation_type from string
        if "automation_type" in data and isinstance(data["automation_type"], str):
            data["automation_type"] = AutomationType(data["automation_type"])

        # Handle kind from string
        if "kind" in data and isinstance(data["kind"], str):
            data["kind"] = AutomationKind(data["kind"])

        return cls(**data)

    def to_manifest_dict(self) -> dict[str, Any]:
        """
        Convert to manifest dictionary format.

        Returns:
            Dictionary suitable for automation.yaml
        """
        data = self.model_dump(exclude_none=True)
        # Convert enums to strings
        if "automation_type" in data:
            data["automation_type"] = data["automation_type"].value
        if "kind" in data:
            data["kind"] = data["kind"].value
        # Ensure schedule_config and webhook_config are dicts for YAML serialization
        if "schedule_config" in data and isinstance(
            self.schedule_config, ScheduleConfig
        ):
            data["schedule_config"] = self.schedule_config.model_dump(exclude_none=True)
        if "webhook_config" in data and isinstance(self.webhook_config, WebhookConfig):
            data["webhook_config"] = self.webhook_config.model_dump(exclude_none=True)
        return data

    def to_yaml(self, path: str | Path) -> None:
        """
        Save metadata to a YAML manifest file.

        Args:
            path: Path to save the automation.yaml file
        """
        path = Path(path)
        data = self.to_manifest_dict()

        with open(path, "w", encoding="utf-8") as f:
            yaml.dump(data, f, default_flow_style=False, sort_keys=False)
