"""Pydantic models for template gallery.

Templates wrap ContainerSpec with gallery metadata for discovery
and sharing of curated container configurations.
"""

from __future__ import annotations

from datetime import datetime
from typing import Annotated, Any

from pydantic import BaseModel, Field, field_validator, model_validator

from gaol.specs.models import ContainerSpec


class CategoryInfo(BaseModel):
    """Category metadata for display in gallery."""

    name: str = Field(..., description="Category identifier (e.g., 'web-dev')")
    display_name: str = Field(..., description="Human-readable name")
    description: str = Field(default="", description="Category description")
    icon: str = Field(default="", description="Emoji icon for category")


class TemplateMetadata(BaseModel):
    """Gallery metadata for templates.

    Contains all the information needed to display and discover
    templates in the gallery without loading the full spec.
    """

    # Identification
    id: Annotated[
        str,
        Field(
            ...,
            min_length=1,
            max_length=128,
            description="Template ID (e.g., 'web-dev/django')",
        ),
    ]
    version: str = Field(default="1.0.0", description="Template version")

    # Display
    display_name: str = Field(..., description="Human-readable name")
    description: str = Field(..., description="Short description")
    long_description: str | None = Field(
        default=None, description="Detailed description (markdown)"
    )

    # Categorization
    category: str = Field(..., description="Category (e.g., 'web-dev', 'data-science')")
    tags: list[str] = Field(default_factory=list, description="Search tags")

    # Attribution
    author: str = Field(default="gaol", description="Template author")
    author_url: str | None = Field(default=None, description="Author URL")
    license: str = Field(default="MIT", description="Template license")

    # Compatibility
    min_gaol_version: str | None = Field(
        default=None, description="Minimum required gaol version"
    )
    supported_runtimes: list[str] = Field(
        default_factory=list,
        description="Supported runtimes (empty = all)",
    )
    supported_distros: list[str] = Field(
        default_factory=list,
        description="Supported distros (empty = all)",
    )

    # Gallery display
    icon: str | None = Field(default=None, description="Emoji icon")
    preview_url: str | None = Field(default=None, description="Screenshot URL")
    repository_url: str | None = Field(default=None, description="Source repository")

    # Statistics (from gallery, not user-editable)
    downloads: int = Field(default=0, description="Download count")
    stars: int = Field(default=0, description="Star count")
    created_at: datetime | None = Field(default=None, description="Creation time")
    updated_at: datetime | None = Field(default=None, description="Last update time")

    @field_validator("id", mode="after")
    @classmethod
    def validate_id_format(cls, v: str) -> str:
        """Validate template ID format (category/name)."""
        if "/" not in v:
            raise ValueError(
                f"Template ID must be in format 'category/name': {v}"
            )
        parts = v.split("/")
        if len(parts) != 2:
            raise ValueError(
                f"Template ID must have exactly one '/': {v}"
            )
        category, name = parts
        if not category or not name:
            raise ValueError(
                f"Template ID category and name cannot be empty: {v}"
            )
        return v

    @property
    def category_from_id(self) -> str:
        """Extract category from template ID."""
        return self.id.split("/")[0]

    @property
    def name_from_id(self) -> str:
        """Extract name from template ID."""
        return self.id.split("/")[1]


class Template(BaseModel):
    """A container template for the gallery.

    Combines gallery metadata with a ContainerSpec to provide
    a complete, shareable container configuration.
    """

    # Gallery metadata
    metadata: TemplateMetadata

    # The actual container specification
    spec: ContainerSpec

    # Additional setup beyond profiles
    post_create_commands: list[str] = Field(
        default_factory=list,
        description="Commands to run after container creation",
    )

    # Documentation
    readme: str | None = Field(
        default=None,
        description="Markdown documentation for the template",
    )

    # Example environment file content
    example_env: dict[str, str] = Field(
        default_factory=dict,
        description="Example environment variables with sample values",
    )

    @model_validator(mode="after")
    def sync_category(self) -> Template:
        """Ensure metadata category matches ID."""
        id_category = self.metadata.category_from_id
        if self.metadata.category != id_category:
            # Update category to match ID
            object.__setattr__(
                self.metadata, "category", id_category
            )
        return self

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

        Handles nested metadata and spec parsing.
        """
        metadata_data = data.get("metadata", {})
        spec_data = data.get("spec", {})

        # If spec doesn't have a name, try to derive from metadata
        if "name" not in spec_data and "id" in metadata_data:
            spec_data["name"] = metadata_data["id"].split("/")[-1]

        return cls(
            metadata=TemplateMetadata(**metadata_data),
            spec=ContainerSpec(**spec_data),
            post_create_commands=data.get("post_create_commands", []),
            readme=data.get("readme"),
            example_env=data.get("example_env", {}),
        )

    def to_dict(self) -> dict[str, Any]:
        """Convert Template to a dictionary for serialization."""
        return {
            "metadata": self.metadata.model_dump(exclude_none=True),
            "spec": self.spec.model_dump(exclude_defaults=True),
            "post_create_commands": self.post_create_commands,
            "readme": self.readme,
            "example_env": self.example_env,
        }


class TemplateEntry(BaseModel):
    """Lightweight entry for gallery index.

    Contains just enough information for listing and searching
    without loading the full template.
    """

    id: str = Field(..., description="Template ID")
    display_name: str = Field(..., description="Display name")
    description: str = Field(..., description="Short description")
    category: str = Field(..., description="Category")
    tags: list[str] = Field(default_factory=list, description="Tags")
    author: str = Field(default="gaol", description="Author")
    version: str = Field(default="1.0.0", description="Version")
    icon: str | None = Field(default=None, description="Icon emoji")

    # Remote location
    template_url: str = Field(..., description="URL to fetch full template")

    # Statistics
    downloads: int = Field(default=0)
    stars: int = Field(default=0)
    updated_at: datetime | None = Field(default=None)

    @classmethod
    def from_template(cls, template: Template, template_url: str) -> TemplateEntry:
        """Create TemplateEntry from a full Template."""
        return cls(
            id=template.metadata.id,
            display_name=template.metadata.display_name,
            description=template.metadata.description,
            category=template.metadata.category,
            tags=template.metadata.tags,
            author=template.metadata.author,
            version=template.metadata.version,
            icon=template.metadata.icon,
            template_url=template_url,
            downloads=template.metadata.downloads,
            stars=template.metadata.stars,
            updated_at=template.metadata.updated_at,
        )


class GalleryIndex(BaseModel):
    """Remote gallery index structure.

    Contains a lightweight index of all available templates
    for efficient listing and searching.
    """

    version: str = Field(default="1", description="Index format version")
    updated_at: datetime = Field(
        default_factory=datetime.now,
        description="Index last updated",
    )

    # Available templates (id -> entry)
    templates: dict[str, TemplateEntry] = Field(
        default_factory=dict,
        description="Template entries indexed by ID",
    )

    # Categories for UI
    categories: dict[str, CategoryInfo] = Field(
        default_factory=dict,
        description="Category metadata",
    )

    def search(
        self,
        query: str,
        category: str | None = None,
        tags: list[str] | None = None,
    ) -> list[TemplateEntry]:
        """Search templates in the index.

        Args:
            query: Search query (matches name, description, tags)
            category: Filter by category
            tags: Filter by tags (all must match)

        Returns:
            List of matching template entries
        """
        query_lower = query.lower()
        results = []

        for entry in self.templates.values():
            # Category filter
            if category and entry.category != category:
                continue

            # Tags filter
            if tags:
                entry_tags_lower = [t.lower() for t in entry.tags]
                if not all(t.lower() in entry_tags_lower for t in tags):
                    continue

            # Query matching
            if query_lower:
                searchable = " ".join([
                    entry.display_name.lower(),
                    entry.description.lower(),
                    " ".join(t.lower() for t in entry.tags),
                    entry.id.lower(),
                ])
                if query_lower not in searchable:
                    continue

            results.append(entry)

        # Sort by downloads (popularity)
        results.sort(key=lambda e: e.downloads, reverse=True)
        return results

    def get_by_category(self, category: str) -> list[TemplateEntry]:
        """Get all templates in a category."""
        return [e for e in self.templates.values() if e.category == category]

    def list_categories(self) -> list[CategoryInfo]:
        """Get all categories with templates."""
        # Get categories that have templates
        used_categories = set(e.category for e in self.templates.values())
        return [
            self.categories.get(cat, CategoryInfo(name=cat, display_name=cat))
            for cat in sorted(used_categories)
        ]


# Default categories
DEFAULT_CATEGORIES: dict[str, CategoryInfo] = {
    "web-dev": CategoryInfo(
        name="web-dev",
        display_name="Web Development",
        description="Web frameworks and development environments",
        icon="🌐",
    ),
    "data-science": CategoryInfo(
        name="data-science",
        display_name="Data Science",
        description="Data analysis, machine learning, and visualization",
        icon="📊",
    ),
    "devops": CategoryInfo(
        name="devops",
        display_name="DevOps",
        description="Infrastructure, CI/CD, and deployment tools",
        icon="🔧",
    ),
    "gaming": CategoryInfo(
        name="gaming",
        display_name="Gaming",
        description="Game servers and streaming",
        icon="🎮",
    ),
    "security": CategoryInfo(
        name="security",
        display_name="Security",
        description="Security tools and penetration testing",
        icon="🔒",
    ),
    "database": CategoryInfo(
        name="database",
        display_name="Databases",
        description="Database servers and management tools",
        icon="🗄️",
    ),
    "media": CategoryInfo(
        name="media",
        display_name="Media",
        description="Media servers and processing tools",
        icon="🎬",
    ),
    "custom": CategoryInfo(
        name="custom",
        display_name="Custom",
        description="User-created templates",
        icon="📦",
    ),
}
