from __future__ import annotations

import re
from dataclasses import dataclass, field
from datetime import datetime
from decimal import Decimal
from enum import StrEnum
from typing import Any, Literal

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

from app.services.clinical_document_projection import render_document_analysis_html


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


class EpicrisisRuleSeverity(StrEnum):
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"


class EpicrisisRuleStatus(StrEnum):
    PASSED = "passed"
    WARNING = "warning"
    BLOCKED = "blocked"


class EpicrisisRuleEvaluationMode(StrEnum):
    DETERMINISTIC_ONLY = "deterministic_only"
    DETERMINISTIC_PLUS_LLM = "deterministic_plus_llm"


class ProcedureClassification(StrEnum):
    QUIRURGICO = "quirurgico"
    NO_QUIRURGICO = "no_quirurgico"
    SIN_CLASIFICAR = "sin_clasificar"


class CostoFacturadoOrigen(StrEnum):
    TOTAL_FACTURA = "total_factura"
    CANTIDAD_POR_VALOR_UNITARIO = "cantidad_por_valor_unitario"
    DESCONOCIDO = "desconocido"


class CostoFacturadoCoincidencia(StrEnum):
    DIRECTA = "directa"
    CODIGO_EXACTO = "codigo_exacto"
    TEXTO_NORMALIZADO_UNICO = "texto_normalizado_unico"
    AMBIGUA = "ambigua"
    INEXISTENTE = "inexistente"


class CostoFacturado(CaseEpicrisisDomainModel):
    valor_total: Decimal | None = Field(default=None, ge=0)
    origen: CostoFacturadoOrigen = CostoFacturadoOrigen.DESCONOCIDO
    coincidencia: CostoFacturadoCoincidencia = CostoFacturadoCoincidencia.INEXISTENTE
    linea_factura_id: str = ""


class ProcedimientoClinico(CaseEpicrisisDomainModel):
    descripcion: str
    codigo_cups: str = ""
    codigo_soat: str = ""
    clasificacion: ProcedureClassification = ProcedureClassification.SIN_CLASIFICAR
    fuentes: list[str] = Field(default_factory=list)
    evidencia: str = ""
    soporte_clinico: bool = True
    clasificacion_manual: bool = False
    costo_facturado: CostoFacturado | None = None


class PdfPreparationSoatGroup(CaseEpicrisisDomainModel):
    procedure_key: str = Field(min_length=1)
    group: int = Field(ge=2, le=23)
    actor: str = Field(min_length=1)
    updated_at: datetime
    origin: Literal["manual_visual"] = "manual_visual"


class PdfPreparationUserMetadata(CaseEpicrisisDomainModel):
    procedure_soat_groups: list[PdfPreparationSoatGroup] = Field(default_factory=list)


class IncapacidadClinica(CaseEpicrisisDomainModel):
    ordenada: bool = False
    fecha_inicio: str = ""
    fecha_fin: str = ""
    duracion: str = ""
    unidad: str = ""
    motivo: str = ""
    recomendacion: str = ""
    evidencia: str = ""


class RecomendacionMedicaCategoria(StrEnum):
    INCAPACIDAD = "incapacidad"
    TRATAMIENTO_CUIDADO = "tratamiento_cuidado"
    RESTRICCION = "restriccion"
    SEGUIMIENTO_REHABILITACION = "seguimiento_rehabilitacion"
    SIGNO_ALARMA = "signo_alarma"
    OTRA = "otra"


class RecomendacionMedica(CaseEpicrisisDomainModel):
    categoria: RecomendacionMedicaCategoria = RecomendacionMedicaCategoria.OTRA
    indicacion: str = ""
    duracion: str = Field(
        default="",
        validation_alias=AliasChoices("duracion", "periodo_duracion", "pd"),
    )
    fecha: str = ""
    pagina: int | None = Field(default=None, ge=1)
    evidencia: str = ""

    @field_validator("indicacion", "duracion", "fecha", "evidencia", mode="before")
    @classmethod
    def _normalize_text(cls, value: Any) -> str:
        return re.sub(r"<[^>]+>", "", str(value or "")).strip()


class AyudaDiagnosticaExportable(CaseEpicrisisDomainModel):
    tipo: str = ""
    nombre: str
    interpretacion: str = ""
    costo_facturado: CostoFacturado | None = None


class EpicrisisRuleFinding(CaseEpicrisisDomainModel):
    rule_code: str
    severity: EpicrisisRuleSeverity
    message: str
    document_types_checked: list[str] = Field(default_factory=list)
    observed_value: Any = None
    blocking: bool = False


class EpicrisisRuleEvaluation(CaseEpicrisisDomainModel):
    status: EpicrisisRuleStatus
    generation_allowed: bool
    ready_for_epicrisis: bool
    findings: list[EpicrisisRuleFinding] = Field(default_factory=list)
    required_documents: list[str] = Field(default_factory=list)
    missing_documents: list[str] = Field(default_factory=list)
    evaluated_at: datetime
    evaluation_mode: EpicrisisRuleEvaluationMode = EpicrisisRuleEvaluationMode.DETERMINISTIC_ONLY


class EpicrisisEvidenceSnapshot(CaseEpicrisisDomainModel):
    case_key: str
    patient_name: str = ""
    case_number: str = ""
    has_quirurgico_document: bool = False
    document_types_available: list[str] = Field(default_factory=list)
    historia_metadatos: dict[str, Any] = Field(default_factory=dict)
    procedimientos_factura: list[str] = Field(default_factory=list)
    procedimientos_historia: list[str] = Field(default_factory=list)
    antecedentes_historia: list[str] = Field(default_factory=list)
    historia_resumen: str = ""
    historia_motivo_consulta: str = ""
    generico_resumenes: list[str] = Field(default_factory=list)
    generico_referencias: list[str] = Field(default_factory=list)
    ayudas_diagnosticas: list[str] = Field(default_factory=list)
    hallazgos_quirurgicos: str = ""
    descripcion_procedimiento: str = ""
    strong_signals: list[str] = Field(default_factory=list)
    support_signals: list[str] = Field(default_factory=list)
    weak_signals: list[str] = Field(default_factory=list)


class EpicrisisSummarySourceUnit(CaseEpicrisisDomainModel):
    identifier: str
    document_type: str
    summary: str
    available_date: str = ""


class EpicrisisSummaryComposition(CaseEpicrisisDomainModel):
    narrative: str
    covered_source_ids: list[str]


class EpicrisisGenerationBlockedError(ValueError):
    def __init__(
        self,
        message: str,
        *,
        evaluation: EpicrisisRuleEvaluation,
        context: dict[str, Any],
    ) -> None:
        super().__init__(message)
        self.evaluation = evaluation
        self.context = dict(context)


@dataclass(frozen=True)
class CaseEpicrisisDocuments:
    historia: dict[str, Any] | None
    quirurgico: dict[str, Any] | None
    factura: dict[str, Any] | None
    radiologia: list[dict[str, Any]]
    laboratorio: list[dict[str, Any]]
    generico: list[dict[str, Any]]
    prescripcion: list[dict[str, Any]] = field(default_factory=list)

    @classmethod
    def from_payload(cls, payload: dict[str, Any]) -> CaseEpicrisisDocuments:
        return cls(
            historia=payload.get("historia"),
            quirurgico=payload.get("quirurgico"),
            factura=payload.get("factura"),
            radiologia=[doc for doc in (payload.get("radiologia") or []) if isinstance(doc, dict)],
            laboratorio=[doc for doc in (payload.get("laboratorio") or []) if isinstance(doc, dict)],
            generico=[doc for doc in (payload.get("generico") or []) if isinstance(doc, dict)],
            prescripcion=[doc for doc in (payload.get("prescripcion") or []) if isinstance(doc, dict)],
        )

    def has_clinical_content(self) -> bool:
        return any(
            (
                self.historia,
                self.quirurgico,
                self.factura,
                self.radiologia,
                self.laboratorio,
                self.generico,
                self.prescripcion,
            )
        )

    @property
    def nombre_paciente(self) -> str:
        associated_doc = next(
            iter([*self.prescripcion, *self.radiologia, *self.laboratorio, *self.generico]), {}
        )
        return (
            (self.historia or {}).get("nombre_paciente")
            or (self.quirurgico or {}).get("nombre_paciente")
            or (self.factura or {}).get("nombre_paciente")
            or associated_doc.get("nombre_paciente")
            or "desconocido"
        )

    @property
    def case_number(self) -> str:
        associated_doc = next(
            iter([*self.prescripcion, *self.radiologia, *self.laboratorio, *self.generico]), {}
        )
        return (
            (self.historia or {}).get("case_number")
            or (self.quirurgico or {}).get("case_number")
            or (self.factura or {}).get("case_number")
            or associated_doc.get("case_number")
            or ""
        )

    @property
    def historia_html(self) -> str | None:
        if not self.historia:
            return None
        return str(
            self.historia.get("analisis_html")
            or render_document_analysis_html(self.historia)
            or ""
        ) or None

    @property
    def quirurgico_html(self) -> str | None:
        if not self.quirurgico:
            return None
        return str(
            self.quirurgico.get("analisis_html")
            or render_document_analysis_html(self.quirurgico)
            or ""
        ) or None


@dataclass(frozen=True)
class PreservedEpicrisisState:
    manual_soat_results: list[dict[str, Any]]
    pdf_draft: dict[str, Any]
    pdf_preparation_user_metadata: dict[str, Any]


@dataclass(frozen=True)
class ClinicalSnapshot:
    metadatos_hc: dict[str, Any]
    antecedentes_hc: list[str]
    antecedentes_hc_estructurados: list[dict[str, Any]]
    procedimientos_hc: list[Any]
    medicamentos_hc: list[Any]
    medicamentos_hc_display: list[str]
    recomendaciones_medicas: list[dict[str, Any]]
    medicamentos_prescripcion: list[Any]
    hallazgos_qx: str
    descripcion_qx: str
    procedimientos_factura: list[dict[str, str]]
    ayudas_diagnosticas: list[dict[str, Any]]
    imagenes_diagnosticas: list[str]


@dataclass(frozen=True)
class CodingSnapshot:
    soat_resultados: list[dict[str, Any]]
    glosa_html: str
    auto_soat_results: list[dict[str, Any]]
    qx_agent_results: list[dict[str, Any]]
