from __future__ import annotations

from collections.abc import Iterable
from dataclasses import dataclass
from html import escape
from typing import Any

from app.llm.schemas import (
    ClinicalStructuredModel,
    DocumentoGenericoStructured,
    DocumentoQuirurgicoStructured,
    FacturaStructured,
    HistoriaClinicaStructured,
    LaboratorioStructured,
    PrefacturaStructured,
    PrescripcionStructured,
    RadiologiaStructured,
    get_clinical_document_model,
)


@dataclass(frozen=True)
class _HtmlCell:
    content: str


def _text(value: Any, default: str = "No especificado") -> str:
    raw = str(value or "").strip()
    return escape(raw or default)


def _list_items(values: list[str]) -> str:
    if not values:
        return "<ol><li>No especificado</li></ol>"
    return "<ol>" + "".join(f"<li>{_text(item)}</li>" for item in values) + "</ol>"


def _paragraph(label: str, value: Any) -> str:
    return f"<p><b>{escape(label)}</b></p><p>{_text(value)}</p>"


def _redacted_paragraph(label: str) -> str:
    return (
        f"<p><b>{escape(label)}</b></p>"
        '<p class="clinical-redacted-value">Dato censurado por protección</p>'
    )


def _is_missing_identity_value(value: Any) -> bool:
    return str(value or "").strip().casefold() in {"", "no especificado", "desconocido"}


def _date_paragraph(label: str, value: Any) -> str:
    return f"<p><b>{escape(label)}</b></p><p>{_render_cell_content(_date_cell(value))}</p>"


def _class_attr(value: str | None) -> str:
    return f' class="{escape(value)}"' if value else ""


def _render_cell_content(value: Any) -> str:
    if isinstance(value, _HtmlCell):
        return value.content
    return _text(value)


def _date_cell(value: Any) -> _HtmlCell | None:
    raw = str(value or "").strip()
    if not raw:
        return None
    escaped = escape(raw)
    return _HtmlCell(f'<time data-local-date="true" datetime="{escaped}">{escaped}</time>')


def _table(
    headers: list[str],
    rows: list[list[Any]],
    *,
    wrapper_class: str = "",
    table_class: str = "",
    column_classes: list[str] | None = None,
    empty_message: str | None = None,
) -> str:
    if not rows:
        if empty_message:
            return (
                f"<div class=\"ui-table-wrap{(' ' + wrapper_class) if wrapper_class else ''}\">"
                f"<p class=\"ui-table-empty\">{escape(empty_message)}</p></div>"
            )
        rows = [["No especificado" for _ in headers]]
    column_classes = column_classes or [""] * len(headers)
    thead = "<tr>" + "".join(
        f"<th{_class_attr(column_classes[index] if index < len(column_classes) else '')}>{escape(header)}</th>"
        for index, header in enumerate(headers)
    ) + "</tr>"
    body_rows = []
    for row in rows:
        cells = []
        for index, cell in enumerate(row):
            cell_class = column_classes[index] if index < len(column_classes) else ""
            cells.append(f"<td{_class_attr(cell_class)}>{_render_cell_content(cell)}</td>")
        body_rows.append("<tr>" + "".join(cells) + "</tr>")
    return (
        f"<div class=\"ui-table-wrap{(' ' + wrapper_class) if wrapper_class else ''}\">"
        f"<table border=\"1\"{_class_attr(table_class)}><thead>"
        + thead
        + "</thead><tbody>"
        + "".join(body_rows)
        + "</tbody></table></div>"
    )


def render_historia_html(
    model: HistoriaClinicaStructured,
    *,
    redacted_identity_fields: Iterable[str] = (),
) -> str:
    redacted_fields = set(redacted_identity_fields)
    diagnosticos = [
        f"{item.codigo} - {item.descripcion}" if item.codigo else item.descripcion
        for item in model.diagnosticos
    ]
    procedimientos = [
        f"{item.codigo} {item.descripcion}" if item.codigo else item.descripcion
        for item in model.procedimientos
    ]
    procedimientos_antecedentes = [
        f"{item.codigo} {item.descripcion}" if item.codigo else item.descripcion
        for item in model.procedimientos_antecedentes
    ]
    medicamentos = []
    for item in model.medicamentos:
        parts = [item.nombre]
        posologia = item.posologia or " ".join(
            part for part in [item.via, item.frecuencia, item.duracion] if part
        )
        if posologia:
            parts.append(posologia)
        if item.dosis:
            parts.append(item.dosis)
        if item.cantidad:
            parts.append(item.cantidad)
        if item.tipo_uso:
            parts.append(item.tipo_uso)
        medicamento = " - ".join(parts)
        if item.codigo:
            medicamento = f"{item.codigo} - {medicamento}"
        medicamentos.append(medicamento)
    return "".join(
        [
            _redacted_paragraph("Nombre del paciente")
            if "patient_name" in redacted_fields and _is_missing_identity_value(model.patient_name)
            else _paragraph("Nombre del paciente", model.patient_name),
            _paragraph("Prestador de servicio", model.prestador_servicio),
            _paragraph("Número de caso", model.numero_caso),
            _redacted_paragraph("Datos de identificación del paciente")
            if "patient_id" in redacted_fields
            and _is_missing_identity_value(model.identificacion_paciente)
            else _paragraph("Datos de identificación del paciente", model.identificacion_paciente),
            _paragraph("Sexo", model.sexo),
            _paragraph("Edad", model.edad),
            _date_paragraph("Fecha de nacimiento", model.fecha_nacimiento),
            _date_paragraph("Fecha de ingreso", model.fecha_ingreso),
            _paragraph("Motivo de consulta", model.motivo_consulta),
            _paragraph("Resumen", model.resumen_clinico),
            f"<p><b>Antecedentes</b></p>{_list_items(model.antecedentes)}",
            f"<p><b>Diagnósticos</b></p>{_list_items(diagnosticos)}",
            f"<p><b>Procedimientos</b></p>{_list_items(procedimientos)}",
            f"<p><b>Procedimientos antecedentes</b></p>{_list_items(procedimientos_antecedentes)}",
            f"<p><b>Medicamentos administrados</b></p>{_list_items(medicamentos)}",
        ]
    )


def render_laboratorio_html(model: LaboratorioStructured) -> str:
    rows = [[item.parametro, item.resultado, item.rango_normal, item.estado] for item in model.resultados]
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            _date_paragraph("Fecha del examen", model.fecha_examen),
            _paragraph("Tipo de examen", model.tipo_examen),
            "<p><b>Resultados principales</b></p>",
            _table(["Parámetro", "Resultado", "Rango normal", "Estado"], rows),
            f"<p><b>Valores alterados</b></p>{_list_items(model.valores_alterados)}",
            _paragraph("Interpretación", model.interpretacion),
            _paragraph("Observaciones", model.observaciones),
        ]
    )


def render_radiologia_html(model: RadiologiaStructured) -> str:
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            _date_paragraph("Fecha del estudio", model.fecha_estudio),
            _paragraph("Tipo de estudio", model.tipo_estudio),
            _paragraph("Región anatómica", model.region_anatomica),
            _paragraph("Técnica", model.tecnica),
            _paragraph("Hallazgos", model.hallazgos),
            _paragraph("Conclusión", model.conclusion),
        ]
    )


def render_prescripcion_html(model: PrescripcionStructured) -> str:
    medicamentos = []
    for item in model.medicamentos:
        parts = [item.nombre]
        if item.dosis:
            parts.append(item.dosis)
        if item.via:
            parts.append(item.via)
        if item.frecuencia:
            parts.append(item.frecuencia)
        if item.cantidad:
            parts.append(item.cantidad)
        medicamentos.append(" - ".join(parts))
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            _date_paragraph("Fecha de prescripción", model.fecha),
            _paragraph("Diagnóstico contexto", model.diagnostico_contexto),
            f"<p><b>Medicamentos formulados</b></p>{_list_items(medicamentos)}",
            _paragraph("Observaciones", model.observaciones),
        ]
    )


def render_quirurgico_html(model: DocumentoQuirurgicoStructured) -> str:
    diagnosticos = [
        f"{item.codigo} - {item.descripcion}" if item.codigo else item.descripcion
        for item in model.diagnosticos
    ]
    procedimientos = [
        f"{item.codigo} {item.descripcion}" if item.codigo else item.descripcion
        for item in model.procedimientos
    ]
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            _date_paragraph("1. FECHA DEL PROCEDIMIENTO", model.fecha_procedimiento),
            f"<p><b>3. DIAGNÓSTICOS</b></p>{_list_items(diagnosticos)}",
            f"<p><b>4. PROCEDIMIENTOS REALIZADOS</b></p>{_list_items(procedimientos)}",
            _paragraph("5. HALLAZGOS QUIRÚRGICOS", model.hallazgos),
            _paragraph("6. DESCRIPCIÓN DEL PROCEDIMIENTO", model.descripcion_procedimiento or model.procedimiento_principal),
            _paragraph("Complicaciones", model.complicaciones),
            _paragraph("Conclusión", model.conclusion),
        ]
    )


def render_factura_html(model: FacturaStructured) -> str:
    servicios = model.servicios_procedimientos
    resumen = model.resumen_financiero
    lineas_canonicas = [
        [
            _date_cell(item.fecha_servicio),
            item.codigo_facturacion,
            item.codigo_referencia,
            item.descripcion,
            item.categoria,
            item.cantidad,
            item.valor_unitario,
            item.total,
        ]
        for item in model.lineas_canonicas
    ]
    compact_value_classes = ["ui-table-cell--wrap", "ui-table-cell--wrap"]
    descriptive_column_classes = [
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--wrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
    ]
    canonical_column_classes = [
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--wrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
        "ui-table-cell--nowrap",
    ]
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            "<p><b>1. INFORMACIÓN DEL PROVEEDOR</b></p>",
            _table(
                ["Campo", "Valor"],
                [
                    ["Nombre de la institución médica", model.proveedor.nombre_institucion],
                    ["NIT", model.proveedor.nit],
                    ["Dirección", model.proveedor.direccion],
                    ["Ciudad y departamento", model.proveedor.ciudad_departamento],
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=compact_value_classes,
            ),
            "<p><b>2. INFORMACIÓN DE LA FACTURA</b></p>",
            _table(
                ["Campo", "Valor"],
                [
                    ["Número de factura", model.datos_factura.numero_factura],
                    ["Prefijo", model.datos_factura.prefijo],
                    ["Fecha de emisión", _date_cell(model.datos_factura.fecha_emision)],
                    ["Número de caso", model.datos_factura.numero_caso],
                    ["Fecha de vencimiento", _date_cell(model.datos_factura.fecha_vencimiento)],
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=compact_value_classes,
            ),
            "<p><b>3. INFORMACIÓN DEL PAGADOR</b></p>",
            _table(
                ["Campo", "Valor"],
                [
                    ["Aseguradora/EPS", model.pagador.aseguradora_eps],
                    ["NIT del pagador", model.pagador.nit_pagador],
                    ["Tipo de convenio", model.pagador.tipo_convenio],
                    ["Número de autorización", model.pagador.numero_autorizacion],
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=compact_value_classes,
            ),
            "<p><b>4. INFORMACIÓN DEL PACIENTE</b></p>",
            _table(
                ["Campo", "Valor"],
                [
                    ["Nombre completo", model.paciente.nombre_completo or model.patient_name],
                    ["Número de identificación (CC)", model.paciente.numero_identificacion],
                    ["Fecha de ingreso", _date_cell(model.paciente.fecha_ingreso)],
                    ["Fecha de egreso", _date_cell(model.paciente.fecha_egreso)],
                    ["Estancia hospitalaria", model.paciente.estancia_hospitalaria],
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=compact_value_classes,
            ),
            "<p><b>5. SERVICIOS Y PROCEDIMIENTOS MÉDICOS</b></p>",
            "<p><b>Líneas facturadas canónicas</b></p>",
            _table(
                [
                    "Fecha",
                    "Código facturación",
                    "Código referencia",
                    "Descripción",
                    "Categoría",
                    "Cantidad",
                    "Valor unitario",
                    "Total",
                ],
                lineas_canonicas,
                wrapper_class="ui-table-wrap--dense-wide",
                table_class="ui-table ui-table--invoice-canonical",
                column_classes=canonical_column_classes,
                empty_message="No se pudieron estructurar líneas facturadas.",
            ),
            "<p><b>Procedimientos quirúrgicos</b></p>",
            _table(
                ["Concepto", "Código CUPS", "Descripción", "Cantidad", "Valor unitario", "Total"],
                [
                    [item.concepto, item.codigo_cups, item.descripcion, item.cantidad, item.valor_unitario, item.total]
                    for item in servicios.procedimientos_quirurgicos
                ],
                wrapper_class="ui-table-wrap--dense-wide",
                column_classes=descriptive_column_classes + ["ui-table-cell--nowrap"],
                empty_message="Sin procedimientos quirúrgicos detectados.",
            ),
            "<p><b>Exámenes de laboratorio</b></p>",
            _table(
                ["Prueba", "Descripción", "Cantidad", "Valor unitario", "Total"],
                [[item.prueba, item.descripcion, item.cantidad, item.valor_unitario, item.total] for item in servicios.examenes_laboratorio],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=[
                    "ui-table-cell--nowrap",
                    "ui-table-cell--wrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                ],
                empty_message="Sin exámenes de laboratorio detectados.",
            ),
            "<p><b>Imagenología</b></p>",
            _table(
                ["Estudio", "Descripción", "Cantidad", "Valor unitario", "Total"],
                [[item.estudio, item.descripcion, item.cantidad, item.valor_unitario, item.total] for item in servicios.imagenologia],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=[
                    "ui-table-cell--nowrap",
                    "ui-table-cell--wrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                ],
                empty_message="Sin estudios de imagen detectados.",
            ),
            "<p><b>Hospitalización</b></p>",
            _table(
                ["Habitación", "Días", "Tarifa", "Total"],
                [[item.habitacion, item.dias, item.tarifa, item.total] for item in servicios.hospitalizacion],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=[
                    "ui-table-cell--wrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                ],
                empty_message="Sin cargos de hospitalización detectados.",
            ),
            "<p><b>Honorarios médicos</b></p>",
            _table(
                ["Rol", "Profesional", "Cantidad", "Valor unitario", "Total"],
                [[item.rol, item.profesional, item.cantidad, item.valor_unitario, item.total] for item in servicios.honorarios_medicos],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=[
                    "ui-table-cell--nowrap",
                    "ui-table-cell--wrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                ],
                empty_message="Sin honorarios médicos detectados.",
            ),
            "<p><b>Medicamentos</b></p>",
            _table(
                ["Medicamento", "Dosis", "Cantidad", "Valor unitario", "Total"],
                [[item.medicamento, item.dosis, item.cantidad, item.valor_unitario, item.total] for item in servicios.medicamentos],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=[
                    "ui-table-cell--wrap",
                    "ui-table-cell--wrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                    "ui-table-cell--nowrap",
                ],
                empty_message="Sin medicamentos detectados.",
            ),
            "<p><b>Otros servicios</b></p>",
            _table(
                ["Código facturación", "Código referencia", "Descripción", "Cantidad", "Valor unitario", "Total"],
                [
                    [
                        item.codigo_facturacion or item.concepto,
                        item.codigo_referencia or item.codigo_cups,
                        item.descripcion,
                        item.cantidad,
                        item.valor_unitario,
                        item.total,
                    ]
                    for item in servicios.otros_servicios
                ],
                wrapper_class="ui-table-wrap--dense-wide",
                column_classes=descriptive_column_classes + ["ui-table-cell--nowrap"],
                empty_message="Sin otros servicios detectados.",
            ),
            "<p><b>6. ANÁLISIS FINANCIERO</b></p>",
            _table(
                ["Concepto", "Valor"],
                [
                    ["Total de servicios", resumen.total_servicios],
                    ["Descuentos", resumen.descuentos],
                    ["Copagos", resumen.copagos],
                    ["Valor total de la factura", resumen.valor_total_factura],
                    ["Valor en letras", resumen.valor_en_letras],
                ],
                wrapper_class="ui-table-wrap--text-heavy",
                column_classes=compact_value_classes,
            ),
            _paragraph("7. OBSERVACIONES IMPORTANTES", resumen.observaciones_importantes),
        ]
    )


def render_prefactura_html(model: PrefacturaStructured) -> str:
    rows = [
        [item.codigo, item.descripcion, item.cantidad, item.valor]
        for item in model.servicios
    ]
    return "".join(
        [
            _paragraph("Nombre del paciente", model.paciente_nombre or model.patient_name),
            _paragraph("Número de caso", model.numero_caso),
            _paragraph("Prestador", model.prestador),
            _paragraph("Identificación del paciente", model.paciente_identificacion),
            "<p><b>Servicios incluidos en prefactura</b></p>",
            _table(["Código", "Descripción", "Cantidad", "Valor"], rows),
            _paragraph("Valor estimado", model.valor_estimado),
            _paragraph("Observaciones", model.observaciones),
        ]
    )


def render_generico_html(model: DocumentoGenericoStructured) -> str:
    referencias = [
        f"{item.tipo}: {item.nombre} - {item.concepto}" if item.concepto else f"{item.tipo}: {item.nombre}"
        for item in model.referencias_diagnosticas
    ]
    return "".join(
        [
            _paragraph("Nombre del paciente", model.patient_name),
            _paragraph("Título", model.titulo or model.tipo_inferido),
            _paragraph("Resumen", model.resumen),
            f"<p><b>Referencias diagnósticas</b></p>{_list_items(referencias)}",
            _paragraph("Observaciones", model.observaciones),
        ]
    )


def render_clinical_document_html(
    model: ClinicalStructuredModel,
    *,
    redacted_identity_fields: Iterable[str] = (),
) -> str:
    if isinstance(model, HistoriaClinicaStructured):
        return render_historia_html(model, redacted_identity_fields=redacted_identity_fields)
    if isinstance(model, LaboratorioStructured):
        return render_laboratorio_html(model)
    if isinstance(model, RadiologiaStructured):
        return render_radiologia_html(model)
    if isinstance(model, PrescripcionStructured):
        return render_prescripcion_html(model)
    if isinstance(model, DocumentoQuirurgicoStructured):
        return render_quirurgico_html(model)
    if isinstance(model, FacturaStructured):
        return render_factura_html(model)
    if isinstance(model, PrefacturaStructured):
        return render_prefactura_html(model)
    if isinstance(model, DocumentoGenericoStructured):
        return render_generico_html(model)
    raise TypeError(f"Modelo clínico no soportado: {type(model)!r}")


def render_clinical_document_html_from_payload(document_type: str, payload: dict[str, Any]) -> str:
    model_cls = get_clinical_document_model(document_type)
    model = model_cls.model_validate(payload)
    return render_clinical_document_html(model)
