"""
Principles - Values-driven evaluation system.

This module provides the schema layer for evaluating items against
principles derived from multiple sources (core, LightWave, Joel Schaefer,
cineOS, etc.).

Key Concepts:
    - Principle: A named value with evaluation criteria
    - PrincipleSource: Where principles come from (core, lightwave, etc.)
    - EvaluationProfile: Pre-configured principle sets for contexts
    - PrincipleScore: Result of evaluating an item against a principle
    - IdealStateGap: Distance between current and ideal state

Usage:
    from lightwave.schema.pydantic.sst import (
        PrinciplesRegistry,
        EvaluationProfile,
        evaluate_against_principles,
    )

    # Load all principles
    registry = PrinciplesRegistry.from_sst()

    # Get evaluation profile for tasks
    profile = registry.get_profile("task_evaluation")

    # Evaluate a task
    result = profile.evaluate(task_data)
    print(f"Score: {result.total_score}/100")
    print(f"Gaps: {result.gaps}")
"""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from functools import lru_cache
from typing import Any

import yaml
from pydantic import BaseModel, ConfigDict, Field

# =============================================================================
# ENUMS
# =============================================================================


class PrincipleAuthority(str, Enum):
    """How much authority a principle source has."""

    ABSOLUTE = "absolute"  # Cannot be overridden (core)
    HIGH = "high"  # Strong authority
    DOMAIN = "domain"  # Domain-specific authority
    MEDIUM = "medium"  # Standard authority
    LOW = "low"  # Suggestions only


class PrincipleCategory(str, Enum):
    """Categories of principles."""

    QUALITY = "quality"
    CRAFT = "craft"
    CLARITY = "clarity"
    EFFICIENCY = "efficiency"
    AUTHENTICITY = "authenticity"
    IMPACT = "impact"


# =============================================================================
# SCORING SCHEMAS
# =============================================================================


class ScoringLevel(BaseModel):
    """A single scoring level within a principle."""

    model_config = ConfigDict(extra="forbid")

    score: int = Field(..., ge=0, le=100)
    condition: str


class PrincipleEvaluation(BaseModel):
    """How to evaluate against a principle."""

    model_config = ConfigDict(extra="forbid")

    question: str = Field(..., description="Question to ask when evaluating")
    ideal_state: str = Field(..., description="What 100% looks like")
    applies_to: list[str] = Field(default_factory=list, description="Item types this applies to")
    scoring: list[ScoringLevel] = Field(default_factory=list)

    def get_score_for_condition(self, condition_met: str) -> int:
        """Get score for a matching condition."""
        for level in self.scoring:
            if level.condition.lower() == condition_met.lower():
                return level.score
        return 0


# =============================================================================
# PRINCIPLE SCHEMAS
# =============================================================================


class Principle(BaseModel):
    """A single principle with evaluation criteria."""

    model_config = ConfigDict(extra="forbid")

    id: str = Field(..., description="Unique principle identifier")
    source: str = Field(..., description="Source of this principle")
    category: PrincipleCategory
    name: str
    statement: str = Field(..., description="The principle statement")
    evaluation: PrincipleEvaluation

    def evaluate(self, item_data: dict[str, Any], assessor_score: int | None = None) -> "PrincipleScore":
        """
        Evaluate an item against this principle.

        Args:
            item_data: Data about the item being evaluated
            assessor_score: Optional pre-computed score (0-100) from agent/human

        Returns:
            PrincipleScore with score and gap analysis
        """
        if assessor_score is not None:
            score = assessor_score
        else:
            # Default to middle score if no assessment provided
            score = 50

        gap = 100 - score
        gap_severity = "none" if gap == 0 else "minor" if gap <= 30 else "moderate" if gap <= 60 else "severe"

        return PrincipleScore(
            principle_id=self.id,
            principle_name=self.name,
            score=score,
            max_score=100,
            gap=gap,
            gap_severity=gap_severity,
            ideal_state=self.evaluation.ideal_state,
            question=self.evaluation.question,
        )


class PrincipleSource(BaseModel):
    """A source of principles."""

    model_config = ConfigDict(extra="forbid")

    id: str
    name: str
    description: str
    authority: PrincipleAuthority
    owner: str
    applies_to: list[dict[str, Any]] | None = None  # List of filter conditions


# =============================================================================
# EVALUATION RESULTS
# =============================================================================


@dataclass
class PrincipleScore:
    """Result of evaluating against a single principle."""

    principle_id: str
    principle_name: str
    score: int
    max_score: int
    gap: int
    gap_severity: str  # none, minor, moderate, severe
    ideal_state: str
    question: str

    @property
    def percentage(self) -> float:
        return (self.score / self.max_score) * 100 if self.max_score > 0 else 0


@dataclass
class EvaluationResult:
    """Complete evaluation result against a profile."""

    profile_name: str
    total_score: float
    max_possible: float
    percentage: float
    principle_scores: list[PrincipleScore]
    gaps: list[PrincipleScore]  # Scores with gap > 0, sorted by severity
    recommendations: list[str]

    @property
    def grade(self) -> str:
        """Letter grade based on percentage."""
        if self.percentage >= 90:
            return "A"
        elif self.percentage >= 80:
            return "B"
        elif self.percentage >= 70:
            return "C"
        elif self.percentage >= 60:
            return "D"
        else:
            return "F"

    @property
    def is_ready(self) -> bool:
        """Whether item meets minimum quality bar (no severe gaps)."""
        return all(g.gap_severity != "severe" for g in self.gaps)


# =============================================================================
# EVALUATION PROFILE
# =============================================================================


class EvaluationProfile(BaseModel):
    """Pre-configured principle set for evaluation contexts."""

    model_config = ConfigDict(extra="forbid")

    id: str
    name: str
    applies_to: list[str] = Field(default_factory=list)
    principle_ids: list[str] = Field(default_factory=list)
    weights: dict[str, float] = Field(default_factory=dict)

    def evaluate(
        self,
        item_data: dict[str, Any],
        principle_scores: dict[str, int],
        registry: "PrinciplesRegistry",
    ) -> EvaluationResult:
        """
        Evaluate an item against this profile's principles.

        Args:
            item_data: Data about the item being evaluated
            principle_scores: Pre-computed scores for each principle (from agent/human)
            registry: The principles registry

        Returns:
            EvaluationResult with scores, gaps, and recommendations
        """
        scores = []
        weighted_sum = 0.0
        total_weight = 0.0

        for pid in self.principle_ids:
            try:
                principle = registry.get_principle(pid)
            except KeyError:
                continue

            # Get score from provided scores or default
            assessor_score = principle_scores.get(pid)
            score = principle.evaluate(item_data, assessor_score)
            scores.append(score)

            # Calculate weighted contribution
            weight = self.weights.get(pid, 1.0 / len(self.principle_ids))
            weighted_sum += score.score * weight
            total_weight += weight

        # Normalize to 100
        total_score = weighted_sum / total_weight if total_weight > 0 else 0
        max_possible = 100.0
        percentage = total_score

        # Find gaps (sorted by severity)
        severity_order = {"severe": 0, "moderate": 1, "minor": 2, "none": 3}
        gaps = [s for s in scores if s.gap > 0]
        gaps.sort(key=lambda g: (severity_order.get(g.gap_severity, 4), -g.gap))

        # Generate recommendations
        recommendations = []
        for gap in gaps[:3]:  # Top 3 gaps
            recommendations.append(
                f"Improve '{gap.principle_name}': {gap.question} (currently {gap.score}%, ideal: {gap.ideal_state})"
            )

        return EvaluationResult(
            profile_name=self.name,
            total_score=total_score,
            max_possible=max_possible,
            percentage=percentage,
            principle_scores=scores,
            gaps=gaps,
            recommendations=recommendations,
        )


# =============================================================================
# PRINCIPLES REGISTRY
# =============================================================================


class PrinciplesRegistry(BaseModel):
    """Complete registry of all principles and evaluation profiles."""

    model_config = ConfigDict(extra="forbid")

    sources: dict[str, PrincipleSource] = Field(default_factory=dict)
    categories: dict[str, dict[str, Any]] = Field(default_factory=dict)
    principles: dict[str, Principle] = Field(default_factory=dict)
    profiles: dict[str, EvaluationProfile] = Field(default_factory=dict)

    @classmethod
    @lru_cache(maxsize=1)
    def from_sst(cls) -> "PrinciplesRegistry":
        """Load complete principles registry from SST."""
        from lightwave.schema.core.paths import get_sst_path

        yaml_path = get_sst_path("principles")
        with open(yaml_path) as f:
            data = yaml.safe_load(f)

        # Parse sources
        sources = {}
        for sid, sdata in data.get("sources", {}).items():
            sources[sid] = PrincipleSource(id=sid, **sdata)

        # Parse categories
        categories = data.get("categories", {})

        # Parse principles
        principles = {}
        for pid, pdata in data.get("principles", {}).items():
            principles[pid] = Principle(id=pid, **pdata)

        # Parse evaluation profiles
        profiles = {}
        for profile_id, profile_data in data.get("evaluation_profiles", {}).items():
            if profile_id == "description":
                continue
            profiles[profile_id] = EvaluationProfile(
                id=profile_id,
                name=profile_data.get("name", profile_id),
                applies_to=profile_data.get("applies_to", []),
                principle_ids=profile_data.get("principles", []),
                weights=profile_data.get("weights", {}),
            )

        return cls(
            sources=sources,
            categories=categories,
            principles=principles,
            profiles=profiles,
        )

    def get_principle(self, principle_id: str) -> Principle:
        """Get a principle by ID."""
        if principle_id not in self.principles:
            available = list(self.principles.keys())
            raise KeyError(f"Principle '{principle_id}' not found. Available: {available}")
        return self.principles[principle_id]

    def get_profile(self, profile_id: str) -> EvaluationProfile:
        """Get an evaluation profile by ID."""
        if profile_id not in self.profiles:
            available = list(self.profiles.keys())
            raise KeyError(f"Profile '{profile_id}' not found. Available: {available}")
        return self.profiles[profile_id]

    def get_principles_by_source(self, source_id: str) -> list[Principle]:
        """Get all principles from a specific source."""
        return [p for p in self.principles.values() if p.source == source_id]

    def get_principles_by_category(self, category: PrincipleCategory) -> list[Principle]:
        """Get all principles in a category."""
        return [p for p in self.principles.values() if p.category == category]

    def get_profile_for_item_type(self, item_type: str) -> EvaluationProfile | None:
        """Find the best matching profile for an item type."""
        for profile in self.profiles.values():
            if item_type in profile.applies_to:
                return profile
        return None

    def evaluate(
        self,
        item_type: str,
        item_data: dict[str, Any],
        principle_scores: dict[str, int],
        profile_id: str | None = None,
    ) -> EvaluationResult:
        """
        Evaluate an item against principles.

        Args:
            item_type: Type of item (task, content, code, etc.)
            item_data: Data about the item
            principle_scores: Pre-computed scores from agent/human evaluation
            profile_id: Optional specific profile to use

        Returns:
            EvaluationResult
        """
        if profile_id:
            profile = self.get_profile(profile_id)
        else:
            profile = self.get_profile_for_item_type(item_type)
            if not profile:
                raise ValueError(f"No evaluation profile found for item type '{item_type}'")

        return profile.evaluate(item_data, principle_scores, self)


# =============================================================================
# CONVENIENCE FUNCTIONS
# =============================================================================


def evaluate_against_principles(
    item_type: str,
    item_data: dict[str, Any],
    principle_scores: dict[str, int],
    profile_id: str | None = None,
) -> EvaluationResult:
    """
    Evaluate an item against its relevant principles.

    This is the main entry point for principle evaluation.

    Args:
        item_type: Type of item (task, content, code, cinematography, etc.)
        item_data: Data about the item being evaluated
        principle_scores: Scores (0-100) for each principle, keyed by principle_id
        profile_id: Optional specific profile to use (auto-detected if not provided)

    Returns:
        EvaluationResult with total score, gaps, and recommendations

    Example:
        result = evaluate_against_principles(
            item_type="task",
            item_data={"title": "...", "has_tests": True},
            principle_scores={
                "core_completeness": 80,
                "core_sst_alignment": 100,
                "lw_cli_first": 70,
                "eng_test_against_spec": 90,
            }
        )
        print(f"Grade: {result.grade}")
        print(f"Ready: {result.is_ready}")
        for rec in result.recommendations:
            print(f"  - {rec}")
    """
    registry = PrinciplesRegistry.from_sst()
    return registry.evaluate(item_type, item_data, principle_scores, profile_id)


def get_principles_for_context(
    item_type: str,
    domain: str | None = None,
) -> list[Principle]:
    """
    Get relevant principles for a given context.

    Args:
        item_type: Type of item (task, content, code, etc.)
        domain: Optional domain (cineos.io, joelschaefer.com, etc.)

    Returns:
        List of applicable principles
    """
    registry = PrinciplesRegistry.from_sst()
    profile = registry.get_profile_for_item_type(item_type)

    if not profile:
        # Return core principles as fallback
        return registry.get_principles_by_source("core")

    principles = []
    for pid in profile.principle_ids:
        try:
            principles.append(registry.get_principle(pid))
        except KeyError:
            continue

    return principles


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

__all__ = [
    # Enums
    "PrincipleAuthority",
    "PrincipleCategory",
    # Schemas
    "Principle",
    "PrincipleSource",
    "PrincipleEvaluation",
    "ScoringLevel",
    "EvaluationProfile",
    # Results
    "PrincipleScore",
    "EvaluationResult",
    # Registry
    "PrinciplesRegistry",
    # Functions
    "evaluate_against_principles",
    "get_principles_for_context",
]
