"""Focused adjudication prompt building for Intelligence Pipeline v3.

Builds adjudication packets containing only relevant chunks and candidates,
uses strict JSON Schema with temperature zero, and enforces a bounded output
budget (max 1536 tokens for decisions, not summaries).
"""

from __future__ import annotations

from typing import Any

from pydantic import BaseModel, Field

from services.intelligence_pipeline_v3.adjudication.schemas import (
    AdjudicationCandidate,
    AdjudicationQuestion,
    ConflictDescription,
    EvidencePacket,
    QuestionCode,
)
from services.intelligence_pipeline_v3.segmenter.models import DocumentChunk

# --- Constants ---

MAX_OUTPUT_TOKENS: int = 1536
"""Maximum tokens for adjudication decisions output. Bounded to prevent
long summaries — the adjudicator produces decisions, not narratives."""

TEMPERATURE: float = 0.0
"""Temperature for adjudication requests. Zero for deterministic output."""

PROMPT_SCHEMA_VERSION: str = "1.0.0"
"""Version of the adjudication prompt schema format."""

PROVIDER_LINEAGE_KEY: str = "adjudication_v3"
"""Lineage identifier for adjudication prompts."""


# --- Models ---


class PromptMetadata(BaseModel):
    """Metadata for the adjudication prompt including version and lineage.

    Tracks prompt version, schema version, and provider lineage for
    reproducibility and auditing.
    """

    prompt_version: str = Field(
        default="1.0.0",
        description="Version of the prompt template",
    )
    schema_version: str = Field(
        default=PROMPT_SCHEMA_VERSION,
        description="Version of the JSON Schema format used",
    )
    provider_lineage: str = Field(
        default=PROVIDER_LINEAGE_KEY,
        description="Identifier for the prompt provider/pipeline stage",
    )
    max_output_tokens: int = Field(
        default=MAX_OUTPUT_TOKENS,
        description="Maximum output token budget for this prompt",
    )
    temperature: float = Field(
        default=TEMPERATURE,
        description="Generation temperature",
    )


class AdjudicationPacket(BaseModel):
    """Complete packet sent to the 9B adjudicator.

    Contains only the information relevant to resolving the specific
    ambiguity — relevant chunks, candidates, conflicts, and questions.
    """

    document_id: str = Field(description="Source document identifier")
    document_type: str = Field(description="Type of document")
    relevant_chunks: list[DocumentChunk] = Field(
        description="Only chunks relevant to the adjudication questions",
    )
    candidates: list[AdjudicationCandidate] = Field(
        description="Candidates requiring adjudication",
    )
    conflicts: list[ConflictDescription] = Field(
        default_factory=list,
        description="Conflicts between candidates",
    )
    questions: list[AdjudicationQuestion] = Field(
        description="Specific questions the adjudicator must answer",
    )
    evidence: list[EvidencePacket] = Field(
        description="Evidence spans available for reference",
    )
    metadata: PromptMetadata = Field(
        default_factory=PromptMetadata,
        description="Prompt metadata for versioning and lineage",
    )


# --- Output schema for strict JSON mode ---


def get_decision_json_schema() -> dict[str, Any]:
    """Return the strict JSON Schema for adjudication decisions.

    Used as the `response_format.json_schema.schema` payload when
    calling the 9B model with strict structured output.
    """
    return {
        "type": "object",
        "properties": {
            "decisions": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "decision_id": {"type": "string"},
                        "question_code": {
                            "type": "string",
                            "enum": [code.value for code in QuestionCode],
                        },
                        "verdict": {
                            "type": "string",
                            "enum": [
                                "accept",
                                "reject",
                                "merge",
                                "split",
                                "reattribute",
                            ],
                        },
                        "candidate_ids": {
                            "type": "array",
                            "items": {"type": "string"},
                            "minItems": 1,
                        },
                        "evidence_ids": {
                            "type": "array",
                            "items": {"type": "string"},
                            "minItems": 1,
                        },
                        "reasoning": {"type": "string"},
                        "resolved_value": {"type": "object"},
                    },
                    "required": [
                        "decision_id",
                        "question_code",
                        "verdict",
                        "candidate_ids",
                        "evidence_ids",
                        "reasoning",
                    ],
                    "additionalProperties": False,
                },
            },
        },
        "required": ["decisions"],
        "additionalProperties": False,
    }


# --- Packet builder ---


def _get_relevant_chunk_ids(
    candidates: list[AdjudicationCandidate],
    conflicts: list[ConflictDescription],
    questions: list[AdjudicationQuestion],
) -> set[str]:
    """Collect chunk IDs referenced by candidates, conflicts, and questions."""
    chunk_ids: set[str] = set()
    for candidate in candidates:
        chunk_ids.update(candidate.source_chunk_ids)
    return chunk_ids


def _filter_relevant_chunks(
    document_chunks: list[DocumentChunk],
    relevant_chunk_ids: set[str],
) -> list[DocumentChunk]:
    """Filter document chunks to include only those referenced by candidates."""
    if not relevant_chunk_ids:
        # If no specific chunks referenced, include all (fallback for
        # cases where chunk IDs weren't specified in candidates)
        return document_chunks
    return [c for c in document_chunks if c.chunk_id in relevant_chunk_ids]


def _collect_evidence_ids(
    candidates: list[AdjudicationCandidate],
    conflicts: list[ConflictDescription],
) -> set[str]:
    """Collect all evidence IDs referenced by candidates and conflicts."""
    evidence_ids: set[str] = set()
    for candidate in candidates:
        evidence_ids.update(candidate.evidence_ids)
    for conflict in conflicts:
        evidence_ids.update(conflict.evidence_ids)
    return evidence_ids


def build_adjudication_packet(
    document_id: str,
    document_type: str,
    document_chunks: list[DocumentChunk],
    candidates: list[AdjudicationCandidate],
    conflicts: list[ConflictDescription],
    questions: list[AdjudicationQuestion],
    evidence: list[EvidencePacket],
    *,
    question_codes: list[str] | None = None,
) -> AdjudicationPacket:
    """Build an adjudication packet with only relevant chunks and evidence.

    Filters document_chunks to include only those referenced by the
    candidates being adjudicated. Ensures the packet is focused and
    within the bounded context the adjudicator expects.

    Args:
        document_id: Source document identifier.
        document_type: Type of document (article, filing, transcript, etc.).
        document_chunks: All available chunks for the document.
        candidates: Candidates requiring adjudication.
        conflicts: Conflicts between candidates.
        questions: Specific questions to resolve.
        evidence: Available evidence spans.
        question_codes: Optional filter to limit questions by code.

    Returns:
        AdjudicationPacket with only relevant chunks included.
    """
    # Filter questions by code if specified
    filtered_questions = questions
    if question_codes:
        code_set = set(question_codes)
        filtered_questions = [
            q for q in questions if q.question_code.value in code_set
        ]

    # Determine which chunks are relevant
    relevant_chunk_ids = _get_relevant_chunk_ids(
        candidates, conflicts, filtered_questions
    )
    relevant_chunks = _filter_relevant_chunks(document_chunks, relevant_chunk_ids)

    # Filter evidence to only include those referenced by candidates/conflicts
    referenced_evidence_ids = _collect_evidence_ids(candidates, conflicts)
    if referenced_evidence_ids:
        relevant_evidence = [
            e for e in evidence if e.evidence_id in referenced_evidence_ids
        ]
    else:
        # Include all evidence if none specifically referenced
        relevant_evidence = evidence

    return AdjudicationPacket(
        document_id=document_id,
        document_type=document_type,
        relevant_chunks=relevant_chunks,
        candidates=candidates,
        conflicts=conflicts,
        questions=filtered_questions,
        evidence=relevant_evidence,
        metadata=PromptMetadata(),
    )


def build_request_payload(packet: AdjudicationPacket) -> dict[str, Any]:
    """Build the full inference request payload for the adjudicator.

    Returns a dict suitable for passing to the inference gateway,
    including strict JSON Schema response format and temperature zero.
    """
    system_prompt = (
        "You are a semantic adjudicator for financial document extraction. "
        "Resolve the ambiguities described in the questions using ONLY the "
        "provided evidence spans. Every decision MUST reference evidence_ids "
        "from the provided evidence. Do NOT estimate confidence, novelty, "
        "impact magnitude, or time horizon — those are computed by separate "
        "calibrated pipelines. Output valid JSON matching the required schema."
    )

    user_content = packet.model_dump_json()

    return {
        "messages": [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_content},
        ],
        "temperature": packet.metadata.temperature,
        "max_tokens": packet.metadata.max_output_tokens,
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "adjudication_response",
                "strict": True,
                "schema": get_decision_json_schema(),
            },
        },
    }
