"""
Constitution Pydantic Schemas.

Type-safe models for the dynamic AI constitution system,
validated against schema/definitions/ai/constitution/base.yaml.

The constitution system has 4 layers:
1. Base Constitution (YAML) - LightWave core principles
2. User Identity (Django) - Legal/financial context
3. User Principles (Django) - Personal values
4. Second Brain (Django) - Weighted knowledge

This module provides:
- ConstitutionConfig: Runtime access to YAML configuration
- Principle: A core behavioral principle
- VoiceModel: A creative voice/tone model
- KnowledgeSource: A reference book/film
- ComposedConstitution: The merged constitution for agent execution

Usage:
    from lightwave.schema.pydantic.models.constitution import (
        ConstitutionConfig,
        ComposedConstitution,
        Principle,
    )

    # Get base principles from YAML
    principles = ConstitutionConfig.get_principles()

    # Compose constitution for agent
    constitution = ComposedConstitution(
        principles=principles,
        identity_context={...},
        knowledge_entries=[...],
    )
"""

from __future__ import annotations

from functools import lru_cache
from pathlib import Path
from typing import Any

import yaml
from pydantic import Field

from lightwave.schema.pydantic.core.base import LightwaveBaseSchema, SSTSchema

# =============================================================================
# YAML CONFIGURATION LOADER
# =============================================================================


def _get_yaml_path() -> Path:
    """Get path to constitution base.yaml (from __index.yaml registry)."""
    from lightwave.schema.core.paths import get_sst_path

    return get_sst_path("constitution_base")


@lru_cache(maxsize=1)
def _load_constitution_yaml() -> dict[str, Any]:
    """Load and cache constitution base.yaml."""
    yaml_path = _get_yaml_path()
    if not yaml_path.exists():
        return {
            "_meta": {"version": "0.0.0"},
            "core_principles": [],
            "creative_ethos": {},
            "knowledge_sources": {"books": [], "films": []},
            "constraints": {"absolute": [], "soft": []},
            "agent_defaults": {},
        }
    with open(yaml_path) as f:
        return yaml.safe_load(f)


class ConstitutionConfig:
    """
    Runtime configuration loaded from constitution base.yaml.

    Provides type-safe access to YAML-defined values with caching.
    """

    @classmethod
    def _yaml(cls) -> dict[str, Any]:
        """Get cached YAML data."""
        return _load_constitution_yaml()

    @classmethod
    def get_version(cls) -> str:
        """Get constitution schema version."""
        return cls._yaml().get("_meta", {}).get("version", "0.0.0")

    # -------------------------------------------------------------------------
    # PRINCIPLES
    # -------------------------------------------------------------------------

    @classmethod
    def get_principles(cls) -> list["Principle"]:
        """Get all core principles as Pydantic models."""
        raw = cls._yaml().get("core_principles", [])
        return [Principle.model_validate(p) for p in raw]

    @classmethod
    def get_principle_ids(cls) -> list[str]:
        """Get all principle IDs."""
        return [p.get("id", "") for p in cls._yaml().get("core_principles", [])]

    @classmethod
    def get_principle_by_id(cls, principle_id: str) -> "Principle | None":
        """Get a specific principle by ID."""
        for p in cls._yaml().get("core_principles", []):
            if p.get("id") == principle_id:
                return Principle.model_validate(p)
        return None

    @classmethod
    def get_principles_by_category(cls, category: str) -> list["Principle"]:
        """Get principles filtered by category."""
        return [
            Principle.model_validate(p) for p in cls._yaml().get("core_principles", []) if p.get("category") == category
        ]

    # -------------------------------------------------------------------------
    # VOICE MODELS
    # -------------------------------------------------------------------------

    @classmethod
    def get_voice_models(cls) -> list["VoiceModel"]:
        """Get all voice models as Pydantic models."""
        raw = cls._yaml().get("creative_ethos", {}).get("voice_models", [])
        return [VoiceModel.model_validate(v) for v in raw]

    @classmethod
    def get_voice_model_ids(cls) -> list[str]:
        """Get all voice model IDs."""
        voice_models = cls._yaml().get("creative_ethos", {}).get("voice_models", [])
        return [v.get("id", "") for v in voice_models]

    @classmethod
    def get_voice_model_by_id(cls, voice_id: str) -> "VoiceModel | None":
        """Get a specific voice model by ID."""
        for v in cls._yaml().get("creative_ethos", {}).get("voice_models", []):
            if v.get("id") == voice_id:
                return VoiceModel.model_validate(v)
        return None

    # -------------------------------------------------------------------------
    # KNOWLEDGE SOURCES
    # -------------------------------------------------------------------------

    @classmethod
    def get_books(cls) -> list["KnowledgeBook"]:
        """Get all book references as Pydantic models."""
        raw = cls._yaml().get("knowledge_sources", {}).get("books", [])
        return [KnowledgeBook.model_validate(b) for b in raw]

    @classmethod
    def get_films(cls) -> list["KnowledgeFilm"]:
        """Get all film references as Pydantic models."""
        raw = cls._yaml().get("knowledge_sources", {}).get("films", [])
        return [KnowledgeFilm.model_validate(f) for f in raw]

    @classmethod
    def get_knowledge_by_relevance(cls, tag: str) -> list["KnowledgeBook"]:
        """Get knowledge sources matching a relevance tag."""
        return [b for b in cls.get_books() if tag in b.relevance]

    # -------------------------------------------------------------------------
    # CREATIVE ETHOS
    # -------------------------------------------------------------------------

    @classmethod
    def get_philosophy(cls) -> str:
        """Get the creative philosophy statement."""
        return cls._yaml().get("creative_ethos", {}).get("philosophy", "")

    @classmethod
    def get_transformation_promise(cls) -> "TransformationPromise":
        """Get the transformation promise."""
        raw = cls._yaml().get("creative_ethos", {}).get("transformation_promise", {})
        return TransformationPromise.model_validate(raw)

    # -------------------------------------------------------------------------
    # CONSTRAINTS
    # -------------------------------------------------------------------------

    @classmethod
    def get_absolute_constraints(cls) -> list["Constraint"]:
        """Get hard constraints that cannot be overridden."""
        raw = cls._yaml().get("constraints", {}).get("absolute", [])
        return [Constraint.model_validate(c) for c in raw]

    @classmethod
    def get_soft_constraints(cls) -> list["WeightedConstraint"]:
        """Get soft constraints with weights."""
        raw = cls._yaml().get("constraints", {}).get("soft", [])
        return [WeightedConstraint.model_validate(c) for c in raw]

    # -------------------------------------------------------------------------
    # AGENT DEFAULTS
    # -------------------------------------------------------------------------

    @classmethod
    def get_agent_defaults(cls, agent_type: str) -> "AgentDefaults":
        """Get default configuration for an agent type."""
        defaults = cls._yaml().get("agent_defaults", {})
        if agent_type in defaults:
            return AgentDefaults.model_validate(defaults[agent_type])
        return AgentDefaults.model_validate(defaults.get("default", {}))

    @classmethod
    def get_agent_types(cls) -> list[str]:
        """Get all defined agent types."""
        return list(cls._yaml().get("agent_defaults", {}).keys())

    @classmethod
    def clear_cache(cls) -> None:
        """Clear the YAML cache (for testing)."""
        _load_constitution_yaml.cache_clear()


# =============================================================================
# BASE CONSTITUTION MODELS (from YAML)
# =============================================================================


class Principle(SSTSchema):
    """
    A core behavioral principle from the base constitution.

    Principles guide agent behavior and can be boosted or reduced
    based on context and agent type.
    """

    id: str = Field(..., description="Unique principle identifier")
    name: str = Field(..., description="Human-readable name")
    description: str = Field(..., description="Full description of the principle")
    weight: float = Field(1.0, ge=0.0, le=2.0, description="Base weight (0.0-2.0)")
    category: str = Field("general", description="Principle category")
    applies_to: list[str] = Field(
        default_factory=lambda: ["all"], description="Agent types this applies to, or ['all']"
    )


class VoiceModel(SSTSchema):
    """
    A creative voice/tone model for content generation.

    Voice models define the personality and style for written content.
    """

    id: str = Field(..., description="Unique voice model identifier")
    name: str = Field(..., description="Human-readable name")
    characteristics: list[str] = Field(default_factory=list, description="Key characteristics of this voice")
    tone: str = Field("", description="Overall tone description")
    use_when: list[str] = Field(default_factory=list, description="Contexts where this voice is appropriate")
    example_phrases: list[str] = Field(default_factory=list, description="Example phrases in this voice")


class KnowledgeBook(SSTSchema):
    """
    A book reference in the knowledge sources.
    """

    id: str = Field(..., description="Unique identifier")
    title: str = Field(..., description="Book title")
    author: str = Field(..., description="Author name")
    relevance: list[str] = Field(default_factory=list, description="Relevant topic tags")
    weight: float = Field(1.0, ge=0.0, le=2.0, description="Importance weight")
    key_insights: list[str] = Field(default_factory=list, description="Key takeaways from this book")


class KnowledgeFilm(SSTSchema):
    """
    A film reference in the knowledge sources.
    """

    id: str = Field(..., description="Unique identifier")
    title: str = Field(..., description="Film title")
    relevance: list[str] = Field(default_factory=list, description="Relevant topic tags")
    weight: float = Field(0.7, ge=0.0, le=2.0, description="Importance weight")
    notes: str = Field("", description="Notes about this reference")


class TransformationPromise(SSTSchema):
    """
    The core transformation promise for users.
    """

    primary: str = Field("", description="Primary transformation statement")
    secondary: str = Field("", description="Secondary transformation")
    visceral: str = Field("", description="Emotional/visceral transformation")
    narrative: str = Field("", description="Full narrative description")


class Constraint(SSTSchema):
    """
    An absolute behavioral constraint that cannot be overridden.
    """

    id: str = Field(..., description="Constraint identifier")
    description: str = Field(..., description="What the constraint prohibits")
    enforceable: bool = Field(True, description="Whether this is hard-enforced")


class WeightedConstraint(SSTSchema):
    """
    A soft constraint with a weight for prioritization.
    """

    id: str = Field(..., description="Constraint identifier")
    description: str = Field(..., description="What the constraint suggests")
    weight: float = Field(0.7, ge=0.0, le=2.0, description="Importance weight")


class AgentDefaults(LightwaveBaseSchema):
    """
    Default configuration for a specific agent type.
    """

    voice_model: str = Field("lightwave_authentic", description="Default voice model ID")
    principles_boost: list[str] = Field(default_factory=list, description="Principle IDs to boost for this agent")
    principles_reduce: list[str] = Field(default_factory=list, description="Principle IDs to reduce for this agent")
    knowledge_filter: list[str] = Field(default_factory=list, description="Relevance tags to filter knowledge sources")


# =============================================================================
# USER IDENTITY CONTEXT
# =============================================================================


class LegalEntityContext(LightwaveBaseSchema):
    """
    User's legal entity information for agent context.
    """

    type: str = Field("individual", description="Entity type (individual, llc, etc.)")
    name: str = Field("", description="Business name if applicable")
    jurisdiction: str = Field("", description="State/country of registration")


class FinancialContext(LightwaveBaseSchema):
    """
    User's financial context for agent behavior.
    """

    accounting_method: str = Field("cash", description="Cash or accrual")
    fiscal_year_end: str = Field("12-31", description="Fiscal year end date")
    credit_profile: str = Field("unknown", description="Credit score range")


class ProfessionalContext(LightwaveBaseSchema):
    """
    User's professional context.
    """

    industry: str = Field("", description="Primary industry")
    experience_years: int | None = Field(None, description="Years in business")
    team_size: str = Field("solo", description="Team size category")


class VerificationContext(LightwaveBaseSchema):
    """
    User's verification status affects trust level in advice.
    """

    identity: bool = Field(False, description="Identity verified")
    business: bool = Field(False, description="Business verified")


class UserIdentityContext(LightwaveBaseSchema):
    """
    Complete user identity context for constitution composition.

    This represents the user's fundamental identity that shapes
    how agents behave and what advice they provide.
    """

    legal_entity: LegalEntityContext = Field(default_factory=LegalEntityContext, description="Legal entity information")
    financial: FinancialContext = Field(default_factory=FinancialContext, description="Financial context")
    professional: ProfessionalContext = Field(default_factory=ProfessionalContext, description="Professional context")
    verification: VerificationContext = Field(default_factory=VerificationContext, description="Verification status")


# =============================================================================
# USER PRINCIPLES
# =============================================================================


class UserPrincipleEntry(LightwaveBaseSchema):
    """
    A user-defined principle that layers on top of base constitution.
    """

    id: str = Field(..., description="Unique identifier")
    name: str = Field(..., description="Principle name")
    description: str = Field(..., description="Full description")
    weight: float = Field(1.0, ge=0.0, le=2.0, description="Importance weight")
    category: str = Field("preference", description="value, preference, constraint, goal")
    overrides_base: str | None = Field(None, description="ID of base principle this overrides")
    applies_to_agents: list[str] = Field(
        default_factory=list, description="Specific agents this applies to (empty = all)"
    )
    is_active: bool = Field(True, description="Whether this principle is active")


# =============================================================================
# SECOND BRAIN ENTRIES
# =============================================================================


class SecondBrainEntryContext(LightwaveBaseSchema):
    """
    A knowledge entry from the user's second brain.

    These are weighted knowledge items that can influence agent behavior.
    """

    id: str = Field(..., description="Entry ID")
    title: str = Field(..., description="Entry title")
    content: str = Field(..., description="Full content")
    summary: str = Field("", description="AI-generated summary")
    entry_type: str = Field("note", description="Type of entry")
    tags: list[str] = Field(default_factory=list, description="Topic tags")
    effective_weight: float = Field(1.0, ge=0.0, le=4.0, description="Combined weight (base * recency * usage)")
    source_url: str = Field("", description="Original source URL")
    is_tenant_specific: bool = Field(False, description="Whether this is tenant-specific or personal")


class KnowledgeLinkContext(LightwaveBaseSchema):
    """
    A link between a Document and a Principle.

    This represents the knowledge graph connection between content
    and principles, enabling context-aware principle retrieval.
    """

    id: str = Field(..., description="Link ID")
    document_id: str = Field(..., description="Document ID")
    document_title: str = Field(..., description="Document title")
    principle_id: str = Field(..., description="Principle ID")
    principle_name: str = Field(..., description="Principle name")
    link_type: str = Field("informs", description="Type of link (source, informs, example, etc.)")
    confidence_score: float = Field(0.8, ge=0.0, le=1.0, description="AI confidence in this link")
    is_ai_generated: bool = Field(False, description="Whether this link was AI-generated")
    reasoning: str = Field("", description="Reasoning for why this link exists")
    supporting_excerpt: str = Field("", description="Excerpt from document supporting this link")


# =============================================================================
# COMPOSED CONSTITUTION
# =============================================================================


class ComposedConstitution(LightwaveBaseSchema):
    """
    The fully composed constitution for an agent execution.

    This is the result of merging all 4 layers:
    1. Base Constitution (YAML)
    2. User Identity (Django)
    3. User Principles (Django)
    4. Second Brain (Django)

    Priority: User overrides > User additions > Base defaults
    """

    # Metadata
    version: str = Field(..., description="Constitution version")
    agent_type: str = Field(..., description="Target agent type")

    # Merged principles (base + user, with weights adjusted)
    principles: list[Principle] = Field(default_factory=list, description="Merged principles with adjusted weights")

    # Voice model for this context
    voice_model: VoiceModel | None = Field(None, description="Selected voice model")

    # Creative ethos
    transformation_promise: TransformationPromise | None = Field(None, description="Transformation promise")
    philosophy: str = Field("", description="Creative philosophy")

    # User identity context
    identity: UserIdentityContext | None = Field(None, description="User's fundamental identity")

    # User's custom principles
    user_principles: list[UserPrincipleEntry] = Field(default_factory=list, description="User-defined principles")

    # Relevant knowledge from second brain
    knowledge: list[SecondBrainEntryContext] = Field(default_factory=list, description="Relevant second brain entries")

    # Knowledge graph links (Document <-> Principle connections)
    knowledge_links: list[KnowledgeLinkContext] = Field(
        default_factory=list, description="Links between documents and principles"
    )

    # Relevant reference material
    reference_books: list[KnowledgeBook] = Field(
        default_factory=list, description="Relevant books from base constitution"
    )

    # Constraints
    absolute_constraints: list[Constraint] = Field(default_factory=list, description="Hard constraints")
    soft_constraints: list[WeightedConstraint] = Field(
        default_factory=list, description="Soft constraints with weights"
    )

    def get_top_principles(self, n: int = 5) -> list[Principle]:
        """Get the top N principles by weight."""
        sorted_principles = sorted(self.principles, key=lambda p: p.weight, reverse=True)
        return sorted_principles[:n]

    def get_principles_for_display(self) -> list[str]:
        """Get principle names formatted for display."""
        return [f"{p.name} ({p.weight:.1f})" for p in self.get_top_principles()]

    @classmethod
    def compose_for_agent(
        cls,
        agent_type: str,
        user_id: str | None = None,
        team_id: str | None = None,
        relevance_tags: list[str] | None = None,
    ) -> "ComposedConstitution":
        """
        Compose a constitution from all 4 layers for agent execution.

        This is the main entry point for building a constitution at runtime.
        It merges:
        1. Base Constitution (YAML) - core principles, voice models, constraints
        2. User Identity (Django) - legal entity, financial context
        3. User Principles (Django) - published principles via KnowledgeLink
        4. Knowledge Context (Django) - relevant documents via KnowledgeLink

        Args:
            agent_type: The type of agent (e.g., "v_accountant", "v_creative")
            user_id: Optional user ID for user-specific context
            team_id: Optional team ID for team-scoped principles
            relevance_tags: Optional list of tags to filter knowledge by

        Returns:
            ComposedConstitution: The fully merged constitution

        Note:
            This method requires Django to be configured to fetch user data.
            In a pure Pydantic context (e.g., testing), use the constructor directly.
        """
        # Get base constitution from YAML
        version = ConstitutionConfig.get_version()
        base_principles = ConstitutionConfig.get_principles()
        agent_defaults = ConstitutionConfig.get_agent_defaults(agent_type)

        # Apply agent defaults (boost/reduce weights)
        adjusted_principles = []
        for principle in base_principles:
            weight = principle.weight
            if principle.id in agent_defaults.principles_boost:
                weight = min(2.0, weight + 0.2)
            elif principle.id in agent_defaults.principles_reduce:
                weight = max(0.0, weight - 0.2)
            adjusted_principles.append(
                Principle(
                    id=principle.id,
                    name=principle.name,
                    description=principle.description,
                    weight=weight,
                    category=principle.category,
                    applies_to=principle.applies_to,
                )
            )

        # Get voice model
        voice_model = ConstitutionConfig.get_voice_model_by_id(agent_defaults.voice_model)

        # Get transformation promise and philosophy
        transformation_promise = ConstitutionConfig.get_transformation_promise()
        philosophy = ConstitutionConfig.get_philosophy()

        # Get constraints
        absolute_constraints = ConstitutionConfig.get_absolute_constraints()
        soft_constraints = ConstitutionConfig.get_soft_constraints()

        # Filter reference books by agent knowledge filter
        all_books = ConstitutionConfig.get_books()
        if agent_defaults.knowledge_filter:
            reference_books = [
                book for book in all_books if any(tag in book.relevance for tag in agent_defaults.knowledge_filter)
            ]
        else:
            reference_books = all_books

        # TODO: Load user-specific data from Django when user_id/team_id provided
        # This would be done via a service layer that queries:
        # - Identity.objects.get(user_id=user_id).profile
        # - Principle.objects.filter(team_id=team_id, status="published")
        # - KnowledgeLink.objects.filter(principle__status="published")

        return cls(
            version=version,
            agent_type=agent_type,
            principles=adjusted_principles,
            voice_model=voice_model,
            transformation_promise=transformation_promise,
            philosophy=philosophy,
            identity=None,  # Populated by Django service layer
            user_principles=[],  # Populated by Django service layer
            knowledge=[],  # Populated by Django service layer
            knowledge_links=[],  # Populated by Django service layer
            reference_books=reference_books,
            absolute_constraints=absolute_constraints,
            soft_constraints=soft_constraints,
        )


# =============================================================================
# MODULE EXPORTS
# =============================================================================

__all__ = [
    # Config
    "ConstitutionConfig",
    # Base constitution models
    "Principle",
    "VoiceModel",
    "KnowledgeBook",
    "KnowledgeFilm",
    "TransformationPromise",
    "Constraint",
    "WeightedConstraint",
    "AgentDefaults",
    # User context models
    "LegalEntityContext",
    "FinancialContext",
    "ProfessionalContext",
    "VerificationContext",
    "UserIdentityContext",
    "UserPrincipleEntry",
    "SecondBrainEntryContext",
    # Knowledge graph context
    "KnowledgeLinkContext",
    # Composed constitution
    "ComposedConstitution",
]
