"""
Page schema definitions from blueprints.yaml.

PageSchemas (formerly Blueprints) define the structure and validation
rules for Page.data JSONField. They use JSONSchema for validation.

NOTE: Renamed from "Blueprint" to "PageSchema" to avoid collision with
ux.PageBlueprint (component layout planning).

SST File: lightwave/schema/definitions/blueprints.yaml
Database Model: sites.PageSchema

NOTE: Island positions and layouts are validated against their respective
SST YAML definitions, not hardcoded here.
"""

from pathlib import Path
from typing import Any, ClassVar

import yaml
from pydantic import Field, field_validator

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


class IslandConfig(SSTSchema):
    """
    Configuration for a default island in a page schema.

    Islands are React components that can be placed in specific positions
    on a page. Each page schema can define default islands.
    """

    name: str = Field(..., description="Island component name (e.g., 'hero', 'features')")
    position: str = Field(..., description="Position in page layout (top, main, bottom, sidebar)")
    variant: str | None = Field(None, description="Variant of the component")

    @field_validator("position")
    @classmethod
    def validate_position(cls, v: str) -> str:
        """Validate position against field_options.yaml."""
        return FieldOptions.validate("island_positions", v)


class PageSchemaDefinition(SSTSchema):
    """
    Page schema (formerly blueprint) from blueprints.yaml.

    Defines the structure and validation rules for page.data JSONField.
    Uses JSONSchema for runtime validation.

    Example:
        schema = PageSchemaDefinition(
            name="marketing-landing",
            display_name="Marketing Landing Page",
            schema={"type": "object", "required": ["headline"]},
            default_layout="web",
        )

    SST Validation:
        schemas = PageSchemaDefinition.get_all_from_sst()
        schema = PageSchemaDefinition.get_by_name("marketing-landing")
    """

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

    name: str = Field(..., description="Schema identifier (e.g., 'marketing-landing', 'pricing')")
    display_name: str = Field(..., description="Human-readable name")
    description: str = Field("", description="Schema description and usage guidelines")
    is_locked: bool = Field(False, description="If True, only platform admins can modify")
    is_active: bool = Field(True, description="Whether schema is available for use")
    allowed_layouts: list[str] = Field(default_factory=list, description="Allowed layout names")
    default_layout: str = Field(..., description="Default layout name")
    default_islands: list[IslandConfig] = Field(default_factory=list, description="Default islands")
    json_schema: dict[str, Any] = Field(
        ...,
        alias="schema",
        serialization_alias="schema",
        description="JSONSchema for page.data validation",
    )

    @field_validator("default_layout")
    @classmethod
    def validate_default_layout(cls, v: str) -> str:
        """Validate default_layout against layouts.yaml."""
        from lightwave.schema.pydantic.models.layouts import LayoutSchema

        return LayoutSchema.validate_layout_name(v)

    @field_validator("allowed_layouts")
    @classmethod
    def validate_allowed_layouts(cls, v: list[str]) -> list[str]:
        """Validate allowed_layouts against layouts.yaml."""
        from lightwave.schema.pydantic.models.layouts import LayoutSchema

        for layout in v:
            LayoutSchema.validate_layout_name(layout)
        return v

    @field_validator("json_schema")
    @classmethod
    def validate_json_schema(cls, v: dict[str, Any]) -> dict[str, Any]:
        """Validate that json_schema is valid JSONSchema."""
        from jsonschema import Draft7Validator

        # This will raise if schema is invalid
        Draft7Validator.check_schema(v)
        return v

    @classmethod
    def get_sst_path(cls) -> Path:
        """Get path to SST YAML file (from __index.yaml registry)."""
        from lightwave.schema.core.paths import get_sst_path

        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_all_from_sst(cls) -> list["PageSchemaDefinition"]:
        """
        Load all page schemas from SST YAML.

        Returns:
            List of PageSchemaDefinition instances
        """
        data = cls.load_sst_data()
        schemas_dict = data.get(cls.SST_KEY, {})

        schemas = []
        for name, schema_data in schemas_dict.items():
            schema_data = dict(schema_data)  # Copy to avoid mutating cache

            # Process default_islands if present
            if "default_islands" in schema_data:
                schema_data["default_islands"] = [
                    IslandConfig(**island) if isinstance(island, dict) else island
                    for island in schema_data["default_islands"]
                ]

            # Ensure name is set (YAML may or may not include it)
            schema_data["name"] = name
            schemas.append(cls(**schema_data))

        return schemas

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

        Args:
            name: Schema name (e.g., "marketing-landing")

        Returns:
            PageSchemaDefinition instance

        Raises:
            KeyError: If schema not found
        """
        data = cls.load_sst_data()
        schemas_dict = data.get(cls.SST_KEY, {})

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

        schema_data = dict(schemas_dict[name])  # Copy to avoid mutating cache

        # Process default_islands if present
        if "default_islands" in schema_data:
            schema_data["default_islands"] = [
                IslandConfig(**island) if isinstance(island, dict) else island
                for island in schema_data["default_islands"]
            ]

        # Ensure name is set (YAML may or may not include it)
        schema_data["name"] = name
        return cls(**schema_data)

    def validate_page_data(self, data: dict[str, Any]) -> None:
        """
        Validate page data against this schema's JSONSchema.

        Args:
            data: Page data to validate

        Raises:
            jsonschema.ValidationError: If data is invalid
        """
        from jsonschema import validate

        validate(data, self.json_schema)

    def get_default_data(self) -> dict[str, Any]:
        """
        Get default data structure from JSONSchema.

        Returns:
            Dictionary with default values from schema properties
        """
        defaults = {}
        properties = self.json_schema.get("properties", {})

        for prop_name, prop_schema in properties.items():
            if "default" in prop_schema:
                defaults[prop_name] = prop_schema["default"]

        return defaults

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

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

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

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

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

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

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

        return errors
