from __future__ import annotations

import re
from datetime import date, datetime, timedelta
from typing import Any, Literal
from zoneinfo import ZoneInfo

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


_FACTURA_EMPTY_DATE_TOKENS = {
    "",
    "no especificado",
    "sin fecha",
    "desconocido",
    "n/a",
    "na",
    "-",
}
_FACTURA_DATE_ONLY_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}$")
_FACTURA_DATE_TIME_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}t\d{2}:\d{2}(?::\d{2})?$", re.IGNORECASE)
_FACTURA_ISO_WITH_ZONE_PATTERN = re.compile(
    r"^\d{4}-\d{2}-\d{2}t\d{2}:\d{2}(?::\d{2})?(?:z|[+-]\d{2}:\d{2})$",
    re.IGNORECASE,
)
_FACTURA_SHORT_DATE_PATTERN = re.compile(
    r"\b(?P<day>\d{1,2})[/-](?P<month>\d{1,2})[/-](?P<year>\d{2,4})"
    r"(?:\s+(?P<hour>\d{1,2}):(?P<minute>\d{2})(?:\s*(?P<meridiem>[ap])\.?\s*m\.?)?)?\b",
    re.IGNORECASE,
)
_FACTURA_LONG_DATE_PATTERN = re.compile(
    r"\b(?P<day>\d{1,2})\s+(?P<month>[a-záéíóúñ]+),\s*(?P<year>\d{4})"
    r"(?:\s+(?P<hour>\d{1,2}):(?P<minute>\d{2})(?:\s*(?P<meridiem>[ap])\.?\s*m\.?)?)?\b",
    re.IGNORECASE,
)
_FACTURA_RELATIVE_DUE_PATTERN = re.compile(r"(?P<days>\d{1,3})\s*d[ií]as?\b", re.IGNORECASE)
_FACTURA_SPANISH_MONTHS = {
    "enero": 1,
    "febrero": 2,
    "marzo": 3,
    "abril": 4,
    "mayo": 5,
    "junio": 6,
    "julio": 7,
    "agosto": 8,
    "septiembre": 9,
    "setiembre": 9,
    "octubre": 10,
    "noviembre": 11,
    "diciembre": 12,
}
_CLINICAL_BOGOTA_TZ = ZoneInfo("America/Bogota")
_HTML_TAG_PATTERN = re.compile(r"<[^>]+>")


def _clean_factura_date_text(value: Any) -> str:
    normalized = re.sub(r"\s+", " ", str(value or "")).strip()
    return normalized.replace(" a. m.", " a.m.").replace(" p. m.", " p.m.")


def _strip_structured_html(value: Any) -> Any:
    if not isinstance(value, str):
        return value
    return re.sub(r"\s+", " ", _HTML_TAG_PATTERN.sub(" ", value)).strip()


def _strip_structured_html_list(value: Any) -> Any:
    if not isinstance(value, list):
        return value
    return [_strip_structured_html(item) for item in value]


def _serialize_clinical_datetime(value: datetime) -> str:
    if value.tzinfo is None:
        value = value.replace(tzinfo=_CLINICAL_BOGOTA_TZ)
    normalized = value.astimezone(_CLINICAL_BOGOTA_TZ).replace(second=0, microsecond=0)
    return normalized.isoformat(timespec="minutes")


def _extract_factura_date_candidate(
    value: Any,
    *,
    allow_time: bool,
) -> str | None:
    cleaned = _clean_factura_date_text(value)
    if not cleaned or cleaned.lower() in _FACTURA_EMPTY_DATE_TOKENS:
        return None

    if _FACTURA_DATE_ONLY_PATTERN.fullmatch(cleaned):
        return cleaned
    if _FACTURA_DATE_TIME_PATTERN.fullmatch(cleaned):
        try:
            parsed = datetime.fromisoformat(cleaned)
        except ValueError:
            return None
        if not allow_time:
            return parsed.date().isoformat()
        return _serialize_clinical_datetime(parsed)
    if _FACTURA_ISO_WITH_ZONE_PATTERN.fullmatch(cleaned):
        try:
            parsed = datetime.fromisoformat(cleaned.replace("Z", "+00:00"))
        except ValueError:
            return None
        if not allow_time:
            return parsed.date().isoformat()
        return _serialize_clinical_datetime(parsed)

    short_match = _FACTURA_SHORT_DATE_PATTERN.search(cleaned)
    if short_match:
        day = int(short_match.group("day"))
        month = int(short_match.group("month"))
        year = int(short_match.group("year"))
        if year < 100:
            year += 2000
        try:
            base_date = date(year, month, day)
        except ValueError:
            return None
        hour_text = short_match.group("hour")
        minute_text = short_match.group("minute")
        if allow_time and hour_text and minute_text:
            hour = int(hour_text)
            minute = int(minute_text)
            meridiem = str(short_match.group("meridiem") or "").lower()
            if meridiem == "p" and hour < 12:
                hour += 12
            if meridiem == "a" and hour == 12:
                hour = 0
            try:
                return _serialize_clinical_datetime(datetime(year, month, day, hour, minute))
            except ValueError:
                return None
        return base_date.isoformat()

    long_match = _FACTURA_LONG_DATE_PATTERN.search(cleaned.lower())
    if long_match:
        day = int(long_match.group("day"))
        month_name = str(long_match.group("month") or "").strip().lower()
        month = _FACTURA_SPANISH_MONTHS.get(month_name)
        year = int(long_match.group("year"))
        if month is None:
            return None
        try:
            base_date = date(year, month, day)
        except ValueError:
            return None
        hour_text = long_match.group("hour")
        minute_text = long_match.group("minute")
        if allow_time and hour_text and minute_text:
            hour = int(hour_text)
            minute = int(minute_text)
            meridiem = str(long_match.group("meridiem") or "").lower()
            if meridiem == "p" and hour < 12:
                hour += 12
            if meridiem == "a" and hour == 12:
                hour = 0
            try:
                return _serialize_clinical_datetime(datetime(year, month, day, hour, minute))
            except ValueError:
                return None
        return base_date.isoformat()

    return None


def normalize_clinical_date_value(
    value: Any,
    *,
    allow_time: bool = False,
) -> str | None:
    return _extract_factura_date_candidate(value, allow_time=allow_time)


def normalize_factura_date_value(
    value: Any,
    *,
    allow_time: bool = False,
) -> str | None:
    """Alias compatible para consumidores existentes del normalizador de factura."""
    return normalize_clinical_date_value(value, allow_time=allow_time)


def normalize_factura_due_date_value(
    value: Any,
    *,
    issue_date_value: Any = None,
) -> str | None:
    cleaned = _clean_factura_date_text(value)
    if not cleaned or cleaned.lower() in _FACTURA_EMPTY_DATE_TOKENS:
        return None

    explicit = _extract_factura_date_candidate(cleaned, allow_time=False)
    if explicit:
        return explicit

    issue_date = _extract_factura_date_candidate(issue_date_value, allow_time=True) or _extract_factura_date_candidate(
        issue_date_value,
        allow_time=False,
    )
    relative_match = _FACTURA_RELATIVE_DUE_PATTERN.search(cleaned)
    if issue_date and relative_match:
        try:
            if "T" in issue_date:
                base_datetime = datetime.fromisoformat(issue_date.replace("Z", "+00:00"))
                base_date = base_datetime.astimezone(_CLINICAL_BOGOTA_TZ).date()
            else:
                base_date = date.fromisoformat(issue_date)
            return (base_date + timedelta(days=int(relative_match.group("days")))).isoformat()
        except ValueError:
            return None
    return None


class ClinicalStructuredModel(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    schema_version: Literal["v1"] = Field(default="v1", alias="sv")
    document_type: str = Field(alias="dt")
    patient_name: str = Field(default="No especificado", alias="pn")
    source_notes: str | None = Field(default=None, alias="sn")

    @field_validator("*", mode="before")
    @classmethod
    def _strip_html_from_strings(cls, value: Any) -> Any:
        return _strip_structured_html(value)


class DiagnosticoItem(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    codigo: str | None = Field(default=None, alias="c")
    descripcion: str = Field(alias="d")

    @field_validator("*", mode="before")
    @classmethod
    def _strip_html_from_strings(cls, value: Any) -> Any:
        return _strip_structured_html(value)


class ProcedimientoItem(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    codigo: str | None = Field(default=None, alias="c")
    descripcion: str = Field(alias="d")

    @field_validator("*", mode="before")
    @classmethod
    def _strip_html_from_strings(cls, value: Any) -> Any:
        return _strip_structured_html(value)


AntecedenteCategoria = Literal[
    "alergico",
    "patologico",
    "quirurgico",
    "farmacologico",
    "toxico",
    "personal",
    "familiar",
    "sistemico",
    "gineco_obstetrico",
    "otro",
]
AntecedenteEstado = Literal["presente", "negado"]
AntecedenteOrigen = Literal["explicito", "inferido"]
AntecedenteConfianza = Literal["alta", "media", "baja"]
RecomendacionMedicaCategoria = Literal[
    "incapacidad",
    "tratamiento_cuidado",
    "restriccion",
    "seguimiento_rehabilitacion",
    "signo_alarma",
    "otra",
]


class AntecedenteClinico(BaseModel):
    """Antecedente trazable, normalizado sin perder el texto clínico fuente."""

    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    categoria: AntecedenteCategoria = Field(default="otro", alias="ct")
    descripcion: str = Field(alias="d")
    contexto: str | None = Field(
        default=None,
        alias="cx",
        description="Contexto farmacológico breve y explícito, separado del nombre del medicamento.",
    )
    estado: AntecedenteEstado = Field(default="presente", alias="es")
    fecha: str | None = Field(default=None, alias="f")
    pagina: int | None = Field(default=None, alias="pg", ge=1)
    evidencia: str | None = Field(default=None, alias="ev")
    discordante: bool = Field(default=False, alias="dc")
    origen: AntecedenteOrigen | None = Field(default=None, alias="og")
    confianza: AntecedenteConfianza | None = Field(default=None, alias="cf")

    @field_validator("descripcion", "contexto", "evidencia", mode="before")
    @classmethod
    def _strip_html_from_strings(cls, value: Any) -> Any:
        return _strip_structured_html(value)


class RecomendacionMedicaItem(BaseModel):
    """Indicación médica vigente con evidencia localizable en la historia."""

    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    categoria: RecomendacionMedicaCategoria = Field(default="otra", alias="ct")
    indicacion: str | None = Field(default=None, alias="i")
    periodo_duracion: str | None = Field(default=None, alias="pd")
    fecha: str | None = Field(default=None, alias="f")
    pagina: int | None = Field(default=None, alias="pg", ge=1)
    evidencia: str | None = Field(default=None, alias="ev")

    @field_validator("indicacion", "periodo_duracion", "fecha", "evidencia", mode="before")
    @classmethod
    def _strip_html_from_strings(cls, value: Any) -> Any:
        cleaned = _strip_structured_html(value)
        return cleaned or None


class MedicamentoItem(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    codigo: str | None = Field(default=None, alias="c")
    nombre: str = Field(alias="n")
    dosis: str | None = Field(default=None, alias="ds")
    posologia: str | None = Field(
        default=None,
        alias="po",
        description="Vía, frecuencia y duración, sin repetir la dosis.",
    )
    via: str | None = Field(default=None, alias="v")
    frecuencia: str | None = Field(default=None, alias="f")
    duracion: str | None = Field(default=None, alias="du")
    cantidad: str | None = Field(default=None, alias="q")
    tipo_uso: str | None = Field(
        default=None,
        alias="tu",
        description="Clasificación clínica: administrado, formulado, ordenado, antecedente u otro.",
    )

    @field_validator("*", mode="before")
    @classmethod
    def _strip_html_from_strings(cls, value: Any) -> Any:
        return _strip_structured_html(value)


class MedicamentoPrescritoItem(MedicamentoItem):
    diagnostico_contexto: str | None = Field(default=None, alias="dx")


class LaboratorioResultadoItem(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    parametro: str = Field(alias="p")
    resultado: str = Field(alias="r")
    rango_normal: str | None = Field(default=None, alias="rn")
    estado: str | None = Field(default=None, alias="e")


class ReferenciaDiagnosticaItem(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    tipo: str = Field(alias="t")
    nombre: str = Field(alias="n")
    concepto: str | None = Field(default=None, alias="c")


class ProveedorFactura(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    nombre_institucion: str | None = Field(default=None, alias="ni")
    nit: str | None = Field(default=None, alias="nt")
    direccion: str | None = Field(default=None, alias="dr")
    ciudad_departamento: str | None = Field(default=None, alias="cd")


class DatosFactura(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    numero_factura: str | None = Field(default=None, alias="nf")
    prefijo: str | None = Field(default=None, alias="pf")
    fecha_emision: str | None = Field(
        default=None,
        alias="fe",
        description=(
            "Fecha de emisión en hora de Colombia, formato ISO `YYYY-MM-DDTHH:mm-05:00` "
            "o `YYYY-MM-DD` si no hay hora."
        ),
        examples=["2024-05-27T11:30-05:00", "2024-05-27"],
    )
    numero_caso: str | None = Field(default=None, alias="nc")
    fecha_vencimiento: str | None = Field(
        default=None,
        alias="fv",
        description=(
            "Fecha de vencimiento en formato ISO `YYYY-MM-DD`. "
            "Si la factura expresa un vencimiento relativo, resolverlo antes de devolverlo."
        ),
        examples=["2024-06-26"],
    )

    @model_validator(mode="before")
    @classmethod
    def _normalize_dates(cls, value: Any) -> Any:
        if not isinstance(value, dict):
            return value
        normalized = dict(value)
        issue_source = normalized.get("fe", normalized.get("fecha_emision"))
        issue_date = normalize_factura_date_value(issue_source, allow_time=True)
        due_source = normalized.get("fv", normalized.get("fecha_vencimiento"))
        due_date = normalize_factura_due_date_value(due_source, issue_date_value=issue_date or issue_source)
        normalized["fe"] = issue_date
        normalized["fv"] = due_date
        return normalized


class PagadorFactura(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    aseguradora_eps: str | None = Field(default=None, alias="ae")
    nit_pagador: str | None = Field(default=None, alias="np")
    tipo_convenio: str | None = Field(default=None, alias="tc")
    numero_autorizacion: str | None = Field(default=None, alias="na")


class PacienteFactura(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    nombre_completo: str | None = Field(default=None, alias="nm")
    numero_identificacion: str | None = Field(default=None, alias="id")
    fecha_ingreso: str | None = Field(
        default=None,
        alias="fi",
        description="Fecha de ingreso en hora de Colombia, formato ISO con `-05:00` o fecha simple.",
        examples=["2024-05-07T09:38-05:00", "2024-05-07"],
    )
    fecha_egreso: str | None = Field(
        default=None,
        alias="fg",
        description="Fecha de egreso en hora de Colombia, formato ISO con `-05:00` o fecha simple.",
        examples=["2024-05-10T11:32-05:00", "2024-05-10"],
    )
    estancia_hospitalaria: str | None = Field(default=None, alias="eh")

    @field_validator("fecha_ingreso", "fecha_egreso", mode="before")
    @classmethod
    def _normalize_datetime_fields(cls, value: Any) -> str | None:
        return normalize_factura_date_value(value, allow_time=True)


class ServicioFacturaItem(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    concepto: str | None = Field(default=None, alias="cp")
    codigo_cups: str | None = Field(default=None, alias="cc")
    fecha_servicio: str | None = Field(
        default=None,
        alias="fs",
        description="Fecha del servicio en formato ISO `YYYY-MM-DD`.",
        examples=["2024-05-07"],
    )
    codigo_facturacion: str | None = Field(default=None, alias="cf")
    codigo_referencia: str | None = Field(default=None, alias="cr")
    descripcion: str | None = Field(default=None, alias="d")
    cantidad: str | None = Field(default=None, alias="q")
    valor_unitario: str | None = Field(default=None, alias="vu")
    total: str | None = Field(default=None, alias="tt")
    prueba: str | None = Field(default=None, alias="pr")
    estudio: str | None = Field(default=None, alias="es")
    habitacion: str | None = Field(default=None, alias="hb")
    dias: str | None = Field(default=None, alias="ds")
    tarifa: str | None = Field(default=None, alias="tf")
    rol: str | None = Field(default=None, alias="rl")
    profesional: str | None = Field(default=None, alias="pf")
    medicamento: str | None = Field(default=None, alias="md")
    dosis: str | None = Field(default=None, alias="do")

    @field_validator("fecha_servicio", mode="before")
    @classmethod
    def _normalize_service_date(cls, value: Any) -> str | None:
        return normalize_factura_date_value(value, allow_time=False)


class FacturaLineaCanonicaItem(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    fecha_servicio: str | None = Field(
        default=None,
        alias="fs",
        description="Fecha del servicio en formato ISO `YYYY-MM-DD`.",
        examples=["2024-05-07"],
    )
    codigo_facturacion: str | None = Field(default=None, alias="cf")
    codigo_referencia: str | None = Field(default=None, alias="cr")
    descripcion: str | None = Field(default=None, alias="d")
    categoria: str | None = Field(default=None, alias="ct")
    cantidad: str | None = Field(default=None, alias="q")
    valor_unitario: str | None = Field(default=None, alias="vu")
    total: str | None = Field(default=None, alias="tt")

    @field_validator("fecha_servicio", mode="before")
    @classmethod
    def _normalize_service_date(cls, value: Any) -> str | None:
        return normalize_factura_date_value(value, allow_time=False)


class ServicioPrefacturaItem(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    codigo: str | None = Field(default=None, alias="c")
    descripcion: str = Field(default="No especificado", alias="d")
    cantidad: str | None = Field(default=None, alias="q")
    valor: str | None = Field(default=None, alias="v")


class ServiciosProcedimientosFactura(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    procedimientos_quirurgicos: list[ServicioFacturaItem] = Field(default_factory=list, alias="pq")
    examenes_laboratorio: list[ServicioFacturaItem] = Field(default_factory=list, alias="el")
    imagenologia: list[ServicioFacturaItem] = Field(default_factory=list, alias="im")
    hospitalizacion: list[ServicioFacturaItem] = Field(default_factory=list, alias="hs")
    honorarios_medicos: list[ServicioFacturaItem] = Field(default_factory=list, alias="hm")
    medicamentos: list[ServicioFacturaItem] = Field(default_factory=list, alias="me")
    otros_servicios: list[ServicioFacturaItem] = Field(default_factory=list, alias="ot")


class ResumenFinancieroFactura(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="ignore")

    total_servicios: str | None = Field(default=None, alias="ts")
    descuentos: str | None = Field(default=None, alias="dc")
    copagos: str | None = Field(default=None, alias="cg")
    valor_total_factura: str | None = Field(default=None, alias="vt")
    valor_en_letras: str | None = Field(default=None, alias="vl")
    observaciones_importantes: str | None = Field(default=None, alias="ob")


class HistoriaClinicaStructured(ClinicalStructuredModel):
    document_type: Literal["historia_clinica"] = Field(default="historia_clinica", alias="dt")
    prestador_servicio: str | None = Field(default=None, alias="ps")
    numero_caso: str | None = Field(default=None, alias="nc")
    identificacion_paciente: str | None = Field(default=None, alias="ip")
    sexo: str | None = Field(default=None, alias="sx")
    edad: str | None = Field(default=None, alias="ed")
    fecha_nacimiento: str | None = Field(default=None, alias="fn")
    fecha_ingreso: str | None = Field(default=None, alias="fi")
    motivo_consulta: str | None = Field(default=None, alias="mc")
    resumen_clinico: str = Field(default="No especificado", alias="rs")
    recomendaciones_medicas: list[RecomendacionMedicaItem] = Field(
        default_factory=list,
        alias="rm",
        description="Indicaciones médicas vigentes, categorizadas y respaldadas por evidencia localizable.",
    )
    antecedentes: list[str] = Field(
        default_factory=list,
        alias="an",
        description="Proyección compatible derivada por el servidor; el extractor debe devolverla vacía.",
    )
    antecedentes_estructurados: list[AntecedenteClinico] = Field(
        default_factory=list,
        alias="ae",
        description="Antecedentes positivos, concisos y respaldados por evidencia localizable.",
    )
    diagnosticos: list[DiagnosticoItem] = Field(default_factory=list, alias="dx")
    procedimientos: list[ProcedimientoItem] = Field(default_factory=list, alias="pr")
    procedimientos_antecedentes: list[ProcedimientoItem] = Field(default_factory=list, alias="pa")
    medicamentos: list[MedicamentoItem] = Field(default_factory=list, alias="md")

    @field_validator("fecha_nacimiento", mode="before")
    @classmethod
    def _normalize_fecha_nacimiento(cls, value: Any) -> str | None:
        return normalize_factura_date_value(value, allow_time=False)

    @field_validator("fecha_ingreso", mode="before")
    @classmethod
    def _normalize_fecha_ingreso(cls, value: Any) -> str | None:
        return normalize_factura_date_value(value, allow_time=True)

    @field_validator("antecedentes", mode="before")
    @classmethod
    def _strip_html_from_antecedentes(cls, value: Any) -> Any:
        return _strip_structured_html_list(value)

    @field_validator("recomendaciones_medicas")
    @classmethod
    def _deduplicate_recomendaciones(
        cls,
        value: list[RecomendacionMedicaItem],
    ) -> list[RecomendacionMedicaItem]:
        unique: list[RecomendacionMedicaItem] = []
        seen: set[tuple[str, str, str]] = set()
        for item in value:
            key = (
                item.categoria,
                str(item.indicacion or "").casefold(),
                str(item.periodo_duracion or "").casefold(),
            )
            if key in seen:
                continue
            seen.add(key)
            unique.append(item)
        return unique


class LaboratorioStructured(ClinicalStructuredModel):
    document_type: Literal["laboratorio"] = Field(default="laboratorio", alias="dt")
    fecha_examen: str | None = Field(default=None, alias="fe")
    tipo_examen: str | None = Field(default=None, alias="te")
    resultados: list[LaboratorioResultadoItem] = Field(default_factory=list, alias="rt")
    valores_alterados: list[str] = Field(default_factory=list, alias="va")
    interpretacion: str | None = Field(default=None, alias="ip")
    observaciones: str | None = Field(default=None, alias="ob")

    @field_validator("fecha_examen", mode="before")
    @classmethod
    def _normalize_fecha_examen(cls, value: Any) -> str | None:
        return normalize_clinical_date_value(value, allow_time=True)


class RadiologiaStructured(ClinicalStructuredModel):
    document_type: Literal["radiologia"] = Field(default="radiologia", alias="dt")
    fecha_estudio: str | None = Field(default=None, alias="fe")
    tipo_estudio: str | None = Field(default=None, alias="te")
    region_anatomica: str | None = Field(default=None, alias="ra")
    tecnica: str | None = Field(default=None, alias="tc")
    hallazgos: str | None = Field(default=None, alias="hl")
    conclusion: str | None = Field(default=None, alias="cl")

    @field_validator("fecha_estudio", mode="before")
    @classmethod
    def _normalize_fecha_estudio(cls, value: Any) -> str | None:
        return normalize_clinical_date_value(value, allow_time=True)


class PrescripcionStructured(ClinicalStructuredModel):
    document_type: Literal["prescripcion"] = Field(default="prescripcion", alias="dt")
    fecha: str | None = Field(default=None, alias="fe")
    diagnostico_contexto: str | None = Field(default=None, alias="dx")
    medicamentos: list[MedicamentoPrescritoItem] = Field(default_factory=list, alias="md")
    observaciones: str | None = Field(default=None, alias="ob")

    @field_validator("fecha", mode="before")
    @classmethod
    def _normalize_fecha(cls, value: Any) -> str | None:
        return normalize_clinical_date_value(value, allow_time=True)


class DocumentoQuirurgicoStructured(ClinicalStructuredModel):
    document_type: Literal["quirurgico"] = Field(default="quirurgico", alias="dt")
    resumen_clinico: str | None = Field(default=None, alias="rs")
    fecha_procedimiento: str | None = Field(default=None, alias="fp")
    procedimiento_principal: str | None = Field(default=None, alias="pp")
    descripcion_procedimiento: str | None = Field(default=None, alias="dp")
    hallazgos: str | None = Field(default=None, alias="hl")
    complicaciones: str | None = Field(default=None, alias="cm")
    conclusion: str | None = Field(default=None, alias="cl")
    diagnosticos: list[DiagnosticoItem] = Field(default_factory=list, alias="dx")
    procedimientos: list[ProcedimientoItem] = Field(default_factory=list, alias="pr")

    @field_validator("fecha_procedimiento", mode="before")
    @classmethod
    def _normalize_fecha_procedimiento(cls, value: Any) -> str | None:
        return normalize_clinical_date_value(value, allow_time=True)


class FacturaStructured(ClinicalStructuredModel):
    document_type: Literal["factura"] = Field(default="factura", alias="dt")
    proveedor: ProveedorFactura = Field(default_factory=ProveedorFactura, alias="pv")
    datos_factura: DatosFactura = Field(default_factory=DatosFactura, alias="df")
    pagador: PagadorFactura = Field(default_factory=PagadorFactura, alias="pg")
    paciente: PacienteFactura = Field(default_factory=PacienteFactura, alias="pc")
    lineas_canonicas: list[FacturaLineaCanonicaItem] = Field(default_factory=list, alias="lc")
    servicios_procedimientos: ServiciosProcedimientosFactura = Field(
        default_factory=ServiciosProcedimientosFactura,
        alias="sp",
    )
    resumen_financiero: ResumenFinancieroFactura = Field(default_factory=ResumenFinancieroFactura, alias="rf")

    @model_validator(mode="before")
    @classmethod
    def _force_factura_document_type(cls, value: Any) -> Any:
        if not isinstance(value, dict):
            return value
        normalized = dict(value)
        normalized["dt"] = "factura"
        normalized["document_type"] = "factura"
        return normalized


class PrefacturaStructured(ClinicalStructuredModel):
    document_type: Literal["prefactura"] = Field(default="prefactura", alias="dt")
    numero_caso: str | None = Field(default=None, alias="nc")
    prestador: str | None = Field(default=None, alias="ps")
    paciente_nombre: str | None = Field(default=None, alias="pnm")
    paciente_identificacion: str | None = Field(default=None, alias="pid")
    servicios: list[ServicioPrefacturaItem] = Field(default_factory=list, alias="svs")
    valor_estimado: str | None = Field(default=None, alias="ve")
    observaciones: str | None = Field(default=None, alias="ob")


class DocumentoGenericoStructured(ClinicalStructuredModel):
    document_type: Literal["generico"] = Field(default="generico", alias="dt")
    titulo: str | None = Field(default=None, alias="tt")
    tipo_inferido: str | None = Field(default=None, alias="ti")
    resumen: str | None = Field(default=None, alias="rs")
    referencias_diagnosticas: list[ReferenciaDiagnosticaItem] = Field(default_factory=list, alias="rd")
    observaciones: str | None = Field(default=None, alias="ob")


CLINICAL_DOCUMENT_MODELS: dict[str, type[ClinicalStructuredModel]] = {
    "historia_clinica": HistoriaClinicaStructured,
    "laboratorio": LaboratorioStructured,
    "radiologia": RadiologiaStructured,
    "prescripcion": PrescripcionStructured,
    "quirurgico": DocumentoQuirurgicoStructured,
    "factura": FacturaStructured,
    "prefactura": PrefacturaStructured,
    "generico": DocumentoGenericoStructured,
}

CLINICAL_SCHEMA_NAMES: dict[str, str] = {
    "historia_clinica": "HistoriaClinicaStructured",
    "laboratorio": "LaboratorioStructured",
    "radiologia": "RadiologiaStructured",
    "prescripcion": "PrescripcionStructured",
    "quirurgico": "DocumentoQuirurgicoStructured",
    "factura": "FacturaStructured",
    "prefactura": "PrefacturaStructured",
    "generico": "DocumentoGenericoStructured",
}


def get_clinical_document_model(document_type: str) -> type[ClinicalStructuredModel]:
    normalized = str(document_type or "").strip().lower() or "generico"
    return CLINICAL_DOCUMENT_MODELS.get(normalized, DocumentoGenericoStructured)


def get_clinical_schema_name(document_type: str) -> str:
    normalized = str(document_type or "").strip().lower() or "generico"
    return CLINICAL_SCHEMA_NAMES.get(normalized, "DocumentoGenericoStructured")


def dump_clinical_model(model: ClinicalStructuredModel) -> dict[str, Any]:
    return model.model_dump(by_alias=True, exclude_none=True)
