"""
Layout schemas from layouts.yaml.

Layouts are the 5 fixed templates that map to authorization levels.
This is a HARD CONSTRAINT - there are exactly 5 layouts.

SST File: lightwave/schema/definitions/layouts.yaml
Database Model: sites.Layout

NOTE: Layout names and auth levels are defined in their respective YAML files,
not hardcoded here. Use get_valid_layout_names() and FieldOptions for validation.
"""

from pathlib import Path
from typing import Any, ClassVar

import yaml
from pydantic import Field, field_validator, model_validator

from lightwave.schema.core.paths import get_sst_path
from lightwave.schema.pydantic.core.base import SSTSchema
from lightwave.schema.pydantic.core.validators import FieldOptions


class LayoutSchema(SSTSchema):
    """
    Layout schema from layouts.yaml.

    Validates:
    - Exactly 5 layouts exist
    - Template paths are valid
    - Auth levels match requirements

    Example:
        layout = LayoutSchema(
            name="web",
            template_path="layouts/web.html",
            description="Public/marketing pages",
            auth_level=None,
            is_active=True,
        )

    SST Validation:
        layouts = LayoutSchema.get_all_from_sst()
        assert len(layouts) == 5
    """

    SST_KEY: ClassVar[str] = "layouts"  # Registry key in __index.yaml

    name: str = Field(..., description="Layout identifier (web, auth, app, app_admin, error)")
    template_path: str = Field(..., description="Template path (e.g., 'layouts/web.html')")
    description: str = Field(..., description="Human-readable description")
    auth_level: str | None = Field(None, description="Required authorization level")
    is_active: bool = Field(True, description="Whether layout is active")
    use_cases: list[str] = Field(default_factory=list, description="Example use cases")
    # context_requirements can be strings or dicts (e.g., "user" or {"is_staff": True})
    context_requirements: list[str | dict[str, Any]] = Field(
        default_factory=list, description="Required context variables"
    )

    @field_validator("auth_level")
    @classmethod
    def validate_auth_level(cls, v: str | None) -> str | None:
        """Validate auth_level against field_options.yaml."""
        if v is None:
            return v
        return FieldOptions.validate("auth_levels", v)

    @model_validator(mode="after")
    def validate_template_path(self) -> "LayoutSchema":
        """Ensure template path follows convention."""
        if not self.template_path.startswith("layouts/"):
            raise ValueError(f"Template path must start with 'layouts/', got: {self.template_path}")
        if not self.template_path.endswith(".html"):
            raise ValueError(f"Template path must end with '.html', got: {self.template_path}")
        return self

    @classmethod
    def get_sst_path(cls) -> Path:
        """Get path to SST YAML file (from __index.yaml registry)."""
        return get_sst_path(cls.SST_KEY)

    @classmethod
    def load_sst_data(cls) -> dict[str, Any]:
        """Load SST YAML file."""
        sst_path = cls.get_sst_path()
        if not sst_path.exists():
            raise FileNotFoundError(f"SST file not found: {sst_path}")
        with open(sst_path) as f:
            return yaml.safe_load(f)

    @classmethod
    def get_valid_layout_names(cls) -> list[str]:
        """
        Get list of valid layout names from SST.

        This is the source of truth for layout names - no hardcoded enum.

        Returns:
            List of layout name strings (e.g., ['web', 'auth', 'app', ...])
        """
        data = cls.load_sst_data()
        return list(data.get(cls.SST_KEY, {}).keys())

    @classmethod
    def validate_layout_name(cls, name: str) -> str:
        """
        Validate a layout name against SST.

        Args:
            name: Layout name to validate

        Returns:
            The validated name

        Raises:
            ValueError: If name is not a valid layout
        """
        valid_names = cls.get_valid_layout_names()
        if name not in valid_names:
            raise ValueError(f"Invalid layout name '{name}'. Valid layouts: {valid_names}")
        return name

    @classmethod
    def get_all_from_sst(cls) -> list["LayoutSchema"]:
        """
        Load all 5 layouts from SST YAML.

        Returns:
            List of LayoutSchema instances (exactly 5)

        Raises:
            ValueError: If not exactly 5 layouts defined
        """
        data = cls.load_sst_data()
        layouts_dict = data.get(cls.SST_KEY, {})

        layouts = []
        for name, layout_data in layouts_dict.items():
            # Add name to data if not present
            layout_data_with_name = {"name": name, **layout_data}
            layouts.append(cls(**layout_data_with_name))

        if len(layouts) != 5:
            raise ValueError(f"Expected exactly 5 layouts, got {len(layouts)}")

        return layouts

    @classmethod
    def get_by_name(cls, name: str) -> "LayoutSchema":
        """
        Get a specific layout by name from SST.

        Args:
            name: Layout name (e.g., "web")

        Returns:
            LayoutSchema instance

        Raises:
            KeyError: If layout not found
        """
        # Validate the name first
        cls.validate_layout_name(name)

        data = cls.load_sst_data()
        layouts_dict = data.get(cls.SST_KEY, {})

        if name not in layouts_dict:
            raise KeyError(f"Layout '{name}' not found in SST. Available: {list(layouts_dict.keys())}")

        layout_data = {"name": name, **layouts_dict[name]}
        return cls(**layout_data)

    @classmethod
    def validate_against_sst(cls, name: str, db_data: dict[str, Any]) -> list[str]:
        """
        Validate database record against SST definition.

        Args:
            name: Layout name
            db_data: Dictionary of database record values

        Returns:
            List of drift errors (empty if valid)
        """
        errors = []

        try:
            sst_layout = cls.get_by_name(name)
        except (KeyError, ValueError) as e:
            return [str(e)]

        # Check template_path
        if db_data.get("template_path") != sst_layout.template_path:
            errors.append(
                f"template_path drift: DB='{db_data.get('template_path')}' vs SST='{sst_layout.template_path}'"
            )

        # Check is_active
        if db_data.get("is_active") != sst_layout.is_active:
            errors.append(f"is_active drift: DB={db_data.get('is_active')} vs SST={sst_layout.is_active}")

        return errors
