"""Typed contracts for the canonical CODE_QA test-first pipeline.

Defines RouterResult, RetrievalRequest, RetrievalResult, EvidenceBundle,
AnswerSynthesisInput, DiagnosticsReport and machine-readable failure reasons.
Used only by the test-first pipeline; legacy runtime is unchanged.
"""

from __future__ import annotations

from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field

# Re-export for pipeline: router output is IntentRouterResult
from app.modules.rag.intent_router_v2.models import IntentRouterResult

# Type alias: router output is the source of truth for the test pipeline
RouterResult = IntentRouterResult


# --- Machine-readable failure reasons for diagnostics ---
FailureReason = Literal[
    "router_low_confidence",
    "target_not_resolved",
    "path_scope_empty",
    "layer_c0_empty",
    "layer_c1_empty",
    "layer_c2_empty",
    "entrypoints_not_found",
    "tests_not_found",
    "insufficient_evidence",
]

FAILURE_REASONS: tuple[FailureReason, ...] = (
    "router_low_confidence",
    "target_not_resolved",
    "path_scope_empty",
    "layer_c0_empty",
    "layer_c1_empty",
    "layer_c2_empty",
    "entrypoints_not_found",
    "tests_not_found",
    "insufficient_evidence",
)


class RetrievalRequest(BaseModel):
    """Request for retrieval stage; built from RouterResult."""

    model_config = ConfigDict(extra="forbid")

    rag_session_id: str
    query: str
    sub_intent: str = "EXPLAIN"
    path_scope: list[str] = Field(default_factory=list)
    keyword_hints: list[str] = Field(default_factory=list)
    symbol_candidates: list[str] = Field(default_factory=list)
    requested_layers: list[str] = Field(default_factory=list)
    # Pass-through for existing adapter (retrieval_spec, retrieval_constraints, query_plan)
    retrieval_spec: Any = None
    retrieval_constraints: Any = None
    query_plan: Any = None


class CodeChunkItem(BaseModel):
    """Single code chunk in normalized retrieval output."""

    model_config = ConfigDict(extra="forbid")

    layer: str
    path: str
    title: str = ""
    content: str = ""
    start_line: int | None = None
    end_line: int | None = None
    metadata: dict[str, Any] = Field(default_factory=dict)


class LayerOutcome(BaseModel):
    """Per-layer retrieval outcome for diagnostics."""

    model_config = ConfigDict(extra="forbid")

    layer_id: str
    hit_count: int = 0
    empty: bool = True
    fallback_used: bool = False


class RetrievalResult(BaseModel):
    """Normalized retrieval result; single structure for all scenarios."""

    model_config = ConfigDict(extra="forbid")

    target_symbol_candidates: list[str] = Field(default_factory=list)
    resolved_symbol: str | None = None
    symbol_resolution_status: str = "not_requested"
    file_candidates: list[str] = Field(default_factory=list)
    code_chunks: list[CodeChunkItem] = Field(default_factory=list)
    relations: list[dict[str, Any]] = Field(default_factory=list)
    entrypoints: list[dict[str, Any]] = Field(default_factory=list)
    test_candidates: list[dict[str, Any]] = Field(default_factory=list)
    layer_outcomes: list[LayerOutcome] = Field(default_factory=list)
    missing_layers: list[str] = Field(default_factory=list)
    raw_rows: list[dict[str, Any]] = Field(default_factory=list)
    retrieval_report: dict[str, Any] = Field(default_factory=dict)


class EvidenceBundle(BaseModel):
    """Canonical evidence bundle for answer synthesis; only source of truth for LLM stage."""

    model_config = ConfigDict(extra="forbid")

    resolved_intent: str = ""
    resolved_sub_intent: str = ""
    resolved_target: str | None = None
    target_type: Literal["symbol", "path", "file", "entrypoint", "none"] = "none"
    target_symbol_candidates: list[str] = Field(default_factory=list)
    file_candidates: list[str] = Field(default_factory=list)
    code_chunks: list[CodeChunkItem] = Field(default_factory=list)
    relations: list[dict[str, Any]] = Field(default_factory=list)
    entrypoints: list[dict[str, Any]] = Field(default_factory=list)
    test_evidence: list[dict[str, Any]] = Field(default_factory=list)
    evidence_count: int = 0
    sufficient: bool = False
    failure_reasons: list[str] = Field(default_factory=list)
    retrieval_summary: dict[str, Any] = Field(default_factory=dict)


class AnswerSynthesisInput(BaseModel):
    """Input for LLM answer synthesis; derived from EvidenceBundle."""

    model_config = ConfigDict(extra="forbid")

    user_question: str = ""
    resolved_scenario: str = ""
    resolved_target: str | None = None
    fast_context: str = ""
    deep_context: str = ""
    evidence_summary: list[dict[str, Any]] = Field(default_factory=list)
    evidence_sufficient: bool = False
    diagnostic_hints: list[str] = Field(default_factory=list)


class DiagnosticsReport(BaseModel):
    """Full diagnostics for the pipeline; Level 1 summary + Level 2 detail."""

    model_config = ConfigDict(extra="forbid")

    # Level 1 — human-readable summary
    intent_correct: bool | None = None
    target_found: bool = False
    layers_used: list[str] = Field(default_factory=list)
    retrieval_sufficient: bool = False
    answer_mode: Literal["normal", "degraded", "insufficient"] = "normal"
    resolved_target: str | None = None
    answer_policy_branch: str = ""
    decision_reason: str = ""

    # Level 2 — detailed
    router_result: dict[str, Any] = Field(default_factory=dict)
    retrieval_request: dict[str, Any] = Field(default_factory=dict)
    per_layer_outcome: list[dict[str, Any]] = Field(default_factory=list)
    empty_layers: list[str] = Field(default_factory=list)
    evidence_gate_decision: dict[str, Any] = Field(default_factory=dict)
    evidence_gate_input: dict[str, Any] = Field(default_factory=dict)
    post_evidence_gate: dict[str, Any] = Field(default_factory=dict)
    failure_reasons: list[str] = Field(default_factory=list)
    timings_ms: dict[str, int] = Field(default_factory=dict)
