from __future__ import annotations

from datetime import datetime
from enum import StrEnum
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator


CURATION_SCHEMA_VERSION = "v1"


class CurationModel(BaseModel):
    model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)


class CodeSystem(StrEnum):
    CIE10 = "cie10"
    CUPS = "cups"
    SOAT = "soat"
    BILLING_INTERNAL = "billing_internal"


class CandidateOrigin(StrEnum):
    EXPLICIT = "explicit"
    RAG = "rag"
    LLM = "llm"
    MANUAL = "manual"


class CurationStatus(StrEnum):
    AUTO_VALIDATED = "validado_automaticamente"
    PENDING_REVIEW = "pendiente_revision"
    EXTERNAL_SERVICE_PENDING = "pendiente_servicio_externo"
    CONFLICT = "conflicto"
    APPROVED = "aprobado"
    CORRECTED = "corregido"
    REJECTED = "rechazado"


class ProcedureState(StrEnum):
    DOCUMENTED = "documentado"
    PERFORMED = "realizado"
    BILLED = "facturado"


class ProcedureReconciliationStatus(StrEnum):
    PERFORMED_AND_BILLED = "realizado_y_facturado"
    PERFORMED_NOT_BILLED = "realizado_no_facturado"
    BILLED_WITHOUT_EXACT_SUPPORT = "facturado_sin_soporte_exacto"
    NON_SURGICAL_BILLED = "no_quirurgico_facturado"


class CurationDecisionType(StrEnum):
    APPROVE = "approve"
    CORRECT = "correct"
    REJECT = "reject"


class EvidenceReference(CurationModel):
    document_id: str = ""
    document_type: str
    filename: str = ""
    page: int | None = Field(default=None, ge=1)
    section: str = ""
    excerpt: str = Field(default="", max_length=280)
    source_hash: str = ""


class PostoperativeSignal(CurationModel):
    term: str
    meaning: str = "postoperatorio"
    page: int | None = Field(default=None, ge=1)
    line_index: int = Field(default=0, ge=0)
    excerpt: str = Field(default="", max_length=280)
    section: str = ""
    block_index: int = Field(default=0, ge=0)
    correlation_status: str = "pendiente_revision"
    correlation_reason: str = ""
    procedure_item_id: str = ""
    document_id: str = ""
    document_type: str = ""
    filename: str = ""


class CatalogReference(CurationModel):
    system: CodeSystem
    index_name: str
    fingerprint: str
    built_at: str = ""
    source_name: str = ""
    available: bool = True


class CurationDecision(CurationModel):
    decision: CurationDecisionType
    actor: str
    decided_at: datetime
    original_code: str = ""
    original_description: str = ""
    corrected_code: str = ""
    corrected_description: str = ""
    reason: str = Field(default="", max_length=500)
    expected_version: str
    selected_soat_code: str = ""
    crosswalk_version: str = ""
    tariff_catalog_version: str = ""
    source_reference: str = ""
    calculate_reference: bool = False
    manual_soat_code: str = ""
    manual_surgical_group: int | None = Field(default=None, ge=2, le=23)
    selected_components: list[str] = Field(default_factory=list)


class SoatCurationCandidate(CurationModel):
    soat_code: str
    description: str
    surgical_group: int | None = None
    reference: str = ""


class SoatCurationValuation(CurationModel):
    year: int
    soat_code: str = ""
    surgical_group: int
    base_tariff: int = Field(ge=0)
    components: dict[str, int] = Field(default_factory=dict)
    catalog_version: str
    source_reference: str
    selected_components: list[str] = Field(default_factory=list)
    liquidated_value: int | None = Field(default=None, ge=0)
    liquidated_components: dict[str, int] = Field(default_factory=dict)
    formula: str = ""
    status: str = "liquidada"
    evidence: list[str] = Field(default_factory=list)
    error: str = ""


class ObjectiveDiagnosis(CurationModel):
    item_id: str
    code_system: CodeSystem = CodeSystem.CIE10
    code: str = ""
    description: str
    original_text: str = ""
    origin: CandidateOrigin
    status: CurationStatus
    evidence: list[EvidenceReference] = Field(default_factory=list)
    catalog: CatalogReference | None = None
    context_type: str = "no_clasificado"
    is_primary: bool = False
    warnings: list[str] = Field(default_factory=list)
    decisions: list[CurationDecision] = Field(default_factory=list)

    @model_validator(mode="after")
    def validate_cie10_system(self) -> ObjectiveDiagnosis:
        if self.code_system != CodeSystem.CIE10:
            raise ValueError("Un diagnóstico objetivo solo puede usar el sistema CIE-10.")
        return self


class ObjectiveProcedure(CurationModel):
    item_id: str
    description: str
    original_text: str = ""
    codigo_cups: str = ""
    codigo_soat: str = ""
    codigo_facturacion: str = ""
    selected_soat_code: str = ""
    soat_candidates: list[SoatCurationCandidate] = Field(default_factory=list)
    selected_soat_valuation: SoatCurationValuation | None = None
    classification: str = "sin_clasificar"
    reconciliation_status: ProcedureReconciliationStatus | None = None
    reconciliation_reason: str = ""
    origin: CandidateOrigin
    status: CurationStatus
    states: list[ProcedureState] = Field(default_factory=list)
    evidence: list[EvidenceReference] = Field(default_factory=list)
    catalog: CatalogReference | None = None
    is_primary: bool = False
    warnings: list[str] = Field(default_factory=list)
    decisions: list[CurationDecision] = Field(default_factory=list)

    @field_validator("states")
    @classmethod
    def deduplicate_states(cls, values: list[ProcedureState]) -> list[ProcedureState]:
        return list(dict.fromkeys(values))


class CodingConflict(CurationModel):
    conflict_id: str
    kind: str
    severity: str = "warning"
    message: str
    item_ids: list[str] = Field(default_factory=list)
    blocking: bool = False


class ObjectiveData(CurationModel):
    schema_version: str = CURATION_SCHEMA_VERSION
    diagnoses: list[ObjectiveDiagnosis] = Field(default_factory=list)
    procedures: list[ObjectiveProcedure] = Field(default_factory=list)
    catalogs: list[CatalogReference] = Field(default_factory=list)
    processing_status: str = "completed"
    retryable_errors: list[str] = Field(default_factory=list)
    postoperative_signals: list[PostoperativeSignal] = Field(default_factory=list)


class CaseCuration(CurationModel):
    schema_version: str = CURATION_SCHEMA_VERSION
    version: str
    diagnoses: list[ObjectiveDiagnosis] = Field(default_factory=list)
    procedures: list[ObjectiveProcedure] = Field(default_factory=list)
    conflicts: list[CodingConflict] = Field(default_factory=list)
    pending_item_ids: list[str] = Field(default_factory=list)
    catalogs: list[CatalogReference] = Field(default_factory=list)
    generation_blocked: bool = False
    blocking_reason: str = ""
    postoperative_signals: list[PostoperativeSignal] = Field(default_factory=list)


def dump_curation_model(model: BaseModel) -> dict[str, Any]:
    return model.model_dump(mode="json", exclude_none=True)
