"""
Learning contract utilities.

Centralizes:
- action_class validation
- mapping TrustProfile -> effective trust tier
- computing learning_tier / learning_eligible using the proof policy matrix

Use this in the receipt ingestion pipeline so agents cannot hand-set learning
fields. Guardian can also reuse the trust tier comparison helper to enforce
action_class requirements.
"""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Optional, Tuple

import yaml

from receipts.policy_matrix import default_matrix
from receipts.trust import TrustMode, TrustProfile


class LearningTier(str, Enum):
    none = "none"
    offline_only = "offline_only"
    low = "low"
    high = "high"


@dataclass(frozen=True)
class LearningComputationResult:
    learning_tier: LearningTier
    learning_eligible: bool
    required_tier: str
    actual_tier: str


def _load_action_classes() -> Tuple[str, ...]:
    """Load allowed action classes from docs/schemas/action_class.yaml."""
    path = (
        Path(__file__)
        .resolve()
        .parent.parent
        / "docs"
        / "schemas"
        / "action_class.yaml"
    )
    if not path.exists():
        raise FileNotFoundError(f"action_class.yaml not found at {path}")
    data = yaml.safe_load(path.read_text())
    classes = data.get("action_class", [])
    if not classes:
        raise ValueError("action_class list missing or empty in action_class.yaml")
    return tuple(classes)


_ACTION_CLASSES = _load_action_classes()


def validate_action_class(action_class: str) -> str:
    """Ensure action_class is valid; returns the same string if valid."""
    if action_class not in _ACTION_CLASSES:
        raise ValueError(
            f"Unknown action_class '{action_class}'. Expected one of {_ACTION_CLASSES}"
        )
    return action_class


def trust_profile_to_tier(profile: TrustProfile) -> str:
    """
    Map TrustProfile to a coarse trust tier for comparison against the matrix.
    Policy assumptions (can be tuned):
    - court_grade -> court_grade
    - mock or offline -> signed_only (acceptable only for low-stakes/offline learning)
    - degraded or missing proofs -> partial
    - otherwise -> partial (default safe lower bound)
    """
    if profile.court_grade:
        return "court_grade"
    if profile.mock or profile.mode == TrustMode.OFFLINE:
        return "signed_only"
    if profile.mode == TrustMode.DEGRADED or profile.missing:
        return "partial"
    return "partial"


def compute_learning_tier(
    *,
    trust_profile: TrustProfile,
    action_class: str,
    environment: str,
    has_override: bool = False,
    has_shadow_disagreement: bool = False,
    intent_aligned: bool = True,
) -> LearningComputationResult:
    """
    Compute learning_tier and learning_eligible from trust + policy + governance signals.

    Rules:
    - If actual trust tier is below the required tier (env, action_class) -> learning_tier = none.
    - Overrides, shadow disagreements, or intent misalignment demote to offline_only (policy can tune to none).
    - Otherwise map trust tier:
        court_grade -> high
        partial -> low
        signed_only -> offline_only
    """
    validate_action_class(action_class)
    matrix = default_matrix()
    required_tier = matrix.required_tier(environment, action_class)
    actual_tier = trust_profile_to_tier(trust_profile)

    if not matrix.tier_at_least(actual_tier, required_tier):
        return LearningComputationResult(
            learning_tier=LearningTier.none,
            learning_eligible=False,
            required_tier=required_tier,
            actual_tier=actual_tier,
        )

    # Governance demotions
    if has_override or has_shadow_disagreement or not intent_aligned:
        tier = LearningTier.offline_only
    else:
        if actual_tier == "court_grade":
            tier = LearningTier.high
        elif actual_tier == "partial":
            tier = LearningTier.low
        else:
            tier = LearningTier.offline_only

    return LearningComputationResult(
        learning_tier=tier,
        learning_eligible=tier != LearningTier.none,
        required_tier=required_tier,
        actual_tier=actual_tier,
    )
