"""DPE configuration models — Pydantic v2."""

from __future__ import annotations

import hashlib
from enum import StrEnum
from typing import Literal

from pydantic import BaseModel, Field, model_validator


class ThrottleBehavior(StrEnum):
    ABORT = "abort"
    DEGRADE_TO_LOCAL = "degrade_to_local"
    RETRY_THEN_DEGRADE = "retry_then_degrade"


class BudgetLimits(BaseModel):
    """Hard execution budget — all limits enforced before every model call."""

    max_total_calls: int = Field(default=10, ge=1)
    max_premium_calls: int = Field(default=6, ge=0)
    max_wall_time_seconds: int = Field(default=900, ge=1)
    max_critique_rounds: int = Field(default=2, ge=0, le=2)
    throttle_behavior: ThrottleBehavior = ThrottleBehavior.ABORT


class ModelAssignment(BaseModel):
    """Which adapter to use for each logical role."""

    ideation_a: str = "claude_code_cli"
    ideation_b: str = "codex_cli"
    critique: str = "claude_code_cli"
    synthesis: str = "claude_code_cli"
    prd_draft: str = "claude_code_cli"
    prd_critique: str = "codex_cli"
    prd_revision: str = "claude_code_cli"
    fallback: str = "local"


class AdapterConfig(BaseModel):
    """Configuration for a single model adapter."""

    adapter_type: Literal[
        "claude_code_cli",
        "codex_cli",
        "local",
        "openai_api",
        "anthropic_api",
    ]
    command_template: str = ""
    model_name: str = ""
    is_premium: bool = False
    timeout_seconds: int = Field(default=120, ge=1)
    enabled: bool = True


class DPEConfig(BaseModel):
    """Root configuration for a DPE run."""

    simulation_mode: bool = False
    smoke_mode: bool = False
    budget: BudgetLimits = Field(default_factory=BudgetLimits)
    model_assignments: ModelAssignment = Field(default_factory=ModelAssignment)
    adapters: dict[str, AdapterConfig] = Field(default_factory=dict)
    output_dir: str = "runs"
    enable_cross_critique: bool = False

    @model_validator(mode="after")
    def _smoke_forces_simulation(self) -> DPEConfig:
        if self.smoke_mode:
            self.simulation_mode = True
        return self

    def config_hash(self) -> str:
        """Deterministic SHA-256 hash of the serialised config.

        In smoke mode, ``output_dir`` is normalised so that two smoke
        runs writing to different directories still produce the same hash.
        """
        if self.smoke_mode:
            normalized = self.model_copy(update={"output_dir": ""})
            payload = normalized.model_dump_json(indent=None)
        else:
            payload = self.model_dump_json(indent=None)
        return hashlib.sha256(payload.encode()).hexdigest()
