"""Value objects validados para extracción, clasificación y cobertura clínica."""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, computed_field


ProcessingFailureSource = Literal["external_provider", "internal", "document"]


class ClinicalPipelineModel(BaseModel):
    model_config = ConfigDict(extra="ignore")

    def to_document(self) -> dict[str, object]:
        return self.model_dump(mode="python")


class PdfExtractionMetadata(ClinicalPipelineModel):
    total_pages: int = Field(default=0, ge=0)
    pages_with_text: int = Field(default=0, ge=0)
    pages_without_text: list[int] = Field(default_factory=list)
    pdfplumber_pages: int = Field(default=0, ge=0)
    pypdf2_fallback_pages: list[int] = Field(default_factory=list)
    character_count: int = Field(default=0, ge=0)
    extractor: str = "pdfplumber+pypdf2"
    warnings: list[str] = Field(default_factory=list)

    @computed_field
    @property
    def page_coverage_ratio(self) -> float:
        if self.total_pages <= 0:
            return 0.0
        return round(self.pages_with_text / self.total_pages, 6)


class PdfExtractionResult(ClinicalPipelineModel):
    text: str = ""
    metadata: PdfExtractionMetadata = Field(default_factory=PdfExtractionMetadata)


class DocumentClassificationDecision(ClinicalPipelineModel):
    document_type: str
    title: str
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    reasons: list[str] = Field(default_factory=list)
    scores: dict[str, float] = Field(default_factory=dict)


class ProcessingFailureMetadata(ClinicalPipelineModel):
    code: str
    source: ProcessingFailureSource
    stage: str
    retryable: bool = False
    provider: str = ""
    model: str = ""
    error_kind: str = ""
    public_title: str
    public_message: str
    failed_chunks: list[int] = Field(default_factory=list)
    attempts: int = Field(default=1, ge=1)


class HistoriaProcessingMetrics(ClinicalPipelineModel):
    source_characters: int = Field(default=0, ge=0)
    covered_characters: int = Field(default=0, ge=0)
    chunks_total: int = Field(default=0, ge=0)
    chunks_succeeded: int = Field(default=0, ge=0)
    consolidation_levels: int = Field(default=0, ge=0)
    chunk_size: int = Field(default=0, ge=0)
    chunk_overlap: int = Field(default=0, ge=0)
    prompt_characters: int = Field(default=0, ge=0)
    chunk_task: str = "historia_chunk_summary"
    final_task: str = "historia_final_summary"
    chunk_provider: str = "gemini"
    chunk_model: str = ""
    final_provider: str = "gemini"
    final_model: str = ""
    failed_chunks: list[int] = Field(default_factory=list)
    source_words: int = Field(default=0, ge=0)
    final_words: int = Field(default=0, ge=0)
    target_words: int = Field(default=0, ge=0)
    acceptable_min_words: int = Field(default=0, ge=0)
    acceptable_max_words: int = Field(default=0, ge=0)
    paragraphs: int = Field(default=0, ge=0)
    quality_retries: int = Field(default=0, ge=0)
    policy_version: str = "v1"
    antecedentes: dict[str, object] = Field(default_factory=dict)

    @computed_field
    @property
    def coverage_ratio(self) -> float:
        if self.source_characters <= 0:
            return 0.0
        return round(min(self.covered_characters, self.source_characters) / self.source_characters, 6)

    @computed_field
    @property
    def complete(self) -> bool:
        return (
            self.source_characters > 0
            and self.covered_characters >= self.source_characters
            and self.chunks_succeeded == self.chunks_total
            and not self.failed_chunks
        )
