"""
Navigation schemas from navigation.yaml.

NavItems define the navigation structure for each site and menu location.
This supports hierarchical menus with children for dropdowns.

SST File: lightwave/schema/definitions/navigation.yaml
Database Model: sites.NavItem

NOTE: Menu locations are defined in navigation.yaml, not hardcoded here.
Use get_valid_menu_locations() for the source of truth.
"""

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 MenuLocationConfig(SSTSchema):
    """
    Configuration for a menu location.

    Defines constraints and behavior for each menu location.
    """

    description: str = Field(..., description="Human-readable description")
    max_depth: int = Field(1, description="Maximum nesting depth")
    mobile_collapsible: bool = Field(False, description="Whether menu collapses on mobile")
    requires_auth: bool = Field(False, description="Whether this menu requires authentication")


class NavItemSchema(SSTSchema):
    """
    Navigation item schema from navigation.yaml.

    Supports hierarchical menus via children field.
    Parent items can have nested children for dropdowns.

    Example:
        nav_item = NavItemSchema(
            label="Features",
            url="/features/",
            order=1,
            is_active=True,
        )

    SST Validation:
        nav_items = NavItemSchema.get_all_for_site("cineos.io", "header")
    """

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

    label: str = Field(..., description="Display text for the nav item")
    url: str = Field(..., description="Target URL (relative or absolute)")
    order: int = Field(..., description="Sort order within menu location")
    is_active: bool = Field(True, description="Whether item is active/visible")
    css_class: str | None = Field(None, description="Optional CSS class(es)")
    open_in_new_tab: bool = Field(False, description="Whether to open link in new tab")
    show_when: str | None = Field(None, description="Visibility condition (guest, authenticated)")
    icon: str | None = Field(None, description="Icon name for sidebar/user menu")
    feature_flag: str | None = Field(None, description="Feature flag required to show item")
    children: list["NavItemSchema"] = Field(default_factory=list, description="Nested child items")

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

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

    @model_validator(mode="after")
    def validate_children_order(self) -> "NavItemSchema":
        """Ensure children have unique orders."""
        if self.children:
            orders = [child.order for child in self.children]
            if len(orders) != len(set(orders)):
                raise ValueError(f"Children must have unique order values, got duplicates in: {orders}")
        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_menu_locations(cls) -> list[str]:
        """
        Get list of valid menu location names from SST.

        This is the source of truth - no hardcoded enum.

        Returns:
            List of menu location strings (e.g., ['header', 'footer_main', ...])
        """
        data = cls.load_sst_data()
        return list(data.get("menu_locations", {}).keys())

    @classmethod
    def validate_menu_location(cls, location: str) -> str:
        """
        Validate a menu location against SST.

        Args:
            location: Menu location to validate

        Returns:
            The validated location

        Raises:
            ValueError: If location is not valid
        """
        valid_locations = cls.get_valid_menu_locations()
        if location not in valid_locations:
            raise ValueError(f"Invalid menu location '{location}'. Valid locations: {valid_locations}")
        return location

    @classmethod
    def get_menu_locations(cls) -> dict[str, MenuLocationConfig]:
        """
        Get all menu location configurations from SST.

        Returns:
            Dictionary mapping location name to MenuLocationConfig
        """
        data = cls.load_sst_data()
        locations_dict = data.get("menu_locations", {})

        return {name: MenuLocationConfig(**config) for name, config in locations_dict.items()}

    @classmethod
    def get_all_for_site(cls, domain: str, location: str) -> list["NavItemSchema"]:
        """
        Get all nav items for a site and menu location from SST.

        Args:
            domain: Site domain (e.g., "cineos.io")
            location: Menu location (e.g., "header")

        Returns:
            List of NavItemSchema instances sorted by order

        Raises:
            KeyError: If site or location not found
        """
        # Validate the location
        cls.validate_menu_location(location)

        data = cls.load_sst_data()
        sites_dict = data.get("sites", {})

        if domain not in sites_dict:
            raise KeyError(f"Site '{domain}' not found in SST. Available: {list(sites_dict.keys())}")

        site_nav = sites_dict[domain]
        if location not in site_nav:
            raise KeyError(
                f"Menu location '{location}' not found for site '{domain}'. Available: {list(site_nav.keys())}"
            )

        nav_items = []
        for item_data in site_nav[location]:
            # Recursively process children
            if "children" in item_data:
                item_data["children"] = [cls(**child) for child in item_data["children"]]
            nav_items.append(cls(**item_data))

        return sorted(nav_items, key=lambda x: x.order)

    @classmethod
    def get_all_sites(cls) -> list[str]:
        """Get list of all site domains with navigation defined."""
        data = cls.load_sst_data()
        return list(data.get("sites", {}).keys())

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

        Args:
            domain: Site domain
            location: Menu location
            order: Item order (part of unique key)
            db_data: Dictionary of database record values

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

        try:
            sst_items = cls.get_all_for_site(domain, location)
        except (KeyError, ValueError) as e:
            return [str(e)]

        # Find matching item by order
        sst_item = next((item for item in sst_items if item.order == order), None)
        if not sst_item:
            return [f"NavItem at order {order} not found in SST for {domain}/{location}"]

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

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

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

        return errors
