"""All value objects and enums for l6e.

These types form the contract between every layer of the package:
- OSS enforcement runtime (gate, store, pipeline)
- SaaS profiler (reads RunSummary.records to generate stage_routing declarations)
"""
from __future__ import annotations

import tomllib
from dataclasses import dataclass, field
from enum import StrEnum
from pathlib import Path
from typing import Literal


class BudgetMode(StrEnum):
    HALT = "halt"
    REROUTE = "reroute"
    WARN = "warn"


class OnBudgetExceeded(StrEnum):
    RAISE = "raise"
    RETURN_PARTIAL = "partial"
    RETURN_EMPTY = "empty"
    RETURN_FALLBACK = "fallback"


class PromptComplexity(StrEnum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"


class StageRoutingHint(StrEnum):
    """Per-stage model tier. OSS: hand-declared. Pro: generated by Task Classification Tuner."""

    LOCAL = "local"
    CLOUD_STANDARD = "cloud_standard"
    CLOUD_FRONTIER = "cloud_frontier"
    INHERIT = "inherit"


class UnknownModelPricingMode(StrEnum):
    WARN_ONLY = "warn_only"
    REROUTE_REQUIRED = "reroute_required"
    HALT_ON_UNKNOWN_PRICING = "halt_on_unknown_pricing"


@dataclass(frozen=True)
class PipelinePolicy:
    """OSS: hand-authored. Pro: generated by profiling agents from run history."""

    budget: float
    budget_mode: BudgetMode = BudgetMode.HALT
    on_budget_exceeded: OnBudgetExceeded = OnBudgetExceeded.RAISE
    fallback_result: str | None = None
    latency_sla: float | None = None  # not enforced in v0.1; reserved for v0.2
    reroute_threshold: float = 0.8
    unknown_model_cost_per_1k_tokens: float = 0.01
    unknown_model_pricing_mode: UnknownModelPricingMode = (
        UnknownModelPricingMode.WARN_ONLY
    )
    stage_routing: dict[str, StageRoutingHint] = field(default_factory=dict)
    stage_overrides: dict[str, BudgetMode] = field(default_factory=dict)

    @classmethod
    def from_toml(cls, path: Path) -> PipelinePolicy:
        """Load a PipelinePolicy from a TOML file.

        Expected structure:
            [policy]            — budget and top-level settings
            [stage_routing]     — stage name → StageRoutingHint value
            [stage_overrides]   — stage name → BudgetMode value
        """
        with open(path, "rb") as f:
            data = tomllib.load(f)

        policy_section = data.get("policy", {})
        stage_routing_raw = data.get("stage_routing", {})
        stage_overrides_raw = data.get("stage_overrides", {})

        stage_routing = {k: StageRoutingHint(v) for k, v in stage_routing_raw.items()}
        stage_overrides = {k: BudgetMode(v) for k, v in stage_overrides_raw.items()}

        return cls(
            budget=float(policy_section["budget"]),
            budget_mode=BudgetMode(policy_section.get("budget_mode", BudgetMode.HALT)),
            on_budget_exceeded=OnBudgetExceeded(
                policy_section.get("on_budget_exceeded", OnBudgetExceeded.RAISE)
            ),
            fallback_result=policy_section.get("fallback_result"),
            latency_sla=policy_section.get("latency_sla"),
            reroute_threshold=float(
                policy_section.get("reroute_threshold", 0.8)
            ),
            unknown_model_cost_per_1k_tokens=float(
                policy_section.get("unknown_model_cost_per_1k_tokens", 0.01)
            ),
            unknown_model_pricing_mode=UnknownModelPricingMode(
                policy_section.get(
                    "unknown_model_pricing_mode",
                    UnknownModelPricingMode.WARN_ONLY,
                )
            ),
            stage_routing=stage_routing,
            stage_overrides=stage_overrides,
        )


@dataclass(frozen=True)
class GateDecision:
    action: Literal["allow", "reroute", "halt"]
    target_model: str
    reason: str


@dataclass(frozen=True)
class BudgetStatus:
    """Zero-token pipeline economics snapshot. Agent-queryable via ctx.budget_status()."""

    run_id: str
    spent_usd: float
    remaining_usd: float
    budget_usd: float
    calls_made: int
    reroutes: int
    pct_used: float
    budget_pressure: Literal["low", "moderate", "high", "critical"]
    # Always None in OSS. Pro populates per-stage routing via PipelinePolicy.
    quality_safe_to_reroute: None = None


@dataclass(frozen=True)
class CallRecord:
    call_index: int
    model_requested: str
    model_used: str
    prompt_tokens: int
    completion_tokens: int
    cost_usd: float
    rerouted: bool
    elapsed_ms: float
    stage: str | None = None
    prompt_complexity: PromptComplexity | None = None
    is_multi_turn: bool = False
    actor_type: str = "parent_agent"
    actor_id: str | None = None
    actor_name: str | None = None
    parent_call_id: str | None = None


@dataclass(frozen=True)
class SubagentSpend:
    actor_id: str
    actor_name: str | None
    calls_made: int
    total_cost_usd: float


@dataclass(frozen=True)
class RunSummary:
    run_id: str
    policy: PipelinePolicy
    total_cost: float
    calls_made: int
    reroutes: int
    savings_usd: float
    records: tuple[CallRecord, ...]
    source: str = "pipeline"  # "mcp" for MCP session runs
    subagent_calls: int = 0
    subagent_spend_usd: float = 0.0
    subagents: tuple[SubagentSpend, ...] = ()
    overhead_usd: float = 0.0
    overhead_calls: int = 0
    net_savings_usd: float = 0.0
    savings_confidence: str = "estimate_only"  # "estimate_only" | "partial_exact" | "exact"