from __future__ import annotations

import re
from dataclasses import dataclass
from datetime import datetime
from typing import TYPE_CHECKING, Annotated, Any

from fastapi import Depends, Request, Security

from app.auth import get_current_user
from app.case_epicrisis.application.cost_ordering import COST_ORDER_VERSION
from app.case_epicrisis.application.use_cases import build_integrated_summary_context
from app.case_epicrisis.application.utils import (
    build_ayudas_diagnosticas,
    extract_recomendaciones_medicas,
    extraer_imagenes_diagnosticas,
    format_ayuda_diagnostica_presentacion,
    group_recomendaciones_medicas_for_view,
    normalize_cie10_entries,
    normalize_context_payload,
    refresh_diagnostico_context,
)
from app.case_epicrisis.application.utils import serialize_doc as serialize_clinical_doc
from app.core.dependencies import get_services
from app.core.logging import get_logger
from app.models import UserInDB
from app.routes.shell_navigation import build_shell_context
from app.services.clinical_document_projection import render_document_analysis_html
from app.services.clinical_processing import (
    extraer_antecedentes_historia,
    extraer_antecedentes_historia_estructurados,
    extraer_medicamentos_historia,
    extraer_metadatos_historia,
    extraer_procedimientos_historia,
    extraer_secciones_quirurgicas,
    formatear_medicamento_canonico,
    normalizar_lista_medicamentos,
)
from app.services.demo_identity_service import get_demo_identity_service
from app.services.factura_html_legacy import (
    extract_factura_procedimientos_from_html,
    is_valid_factura_procedimiento,
)
from app.services.historia_antecedentes import group_antecedentes_for_view
from app.services.soat_processing import _construir_soat_y_glosa, generar_codigos_desde_soat_con_gemini


if TYPE_CHECKING:
    from app.core.services import AppServices
else:
    AppServices = Any

logger = get_logger(__name__)
EPICRISIS_PDF_CACHE_COLLECTION = "epicrisis_pdf_cache"
CurrentUser = Annotated[UserInDB, Security(get_current_user)]
ServicesDep = Annotated[AppServices, Depends(get_services)]
LEGACY_EPICRISIS_ID_WARNING = (
    '299 - "The /epicrisis?id=... legacy flow is deprecated; use /epicrisis?case_key=... instead."'
)


@dataclass(frozen=True)
class EpicrisisDocumentBundle:
    nombre_paciente: str
    historia: dict[str, Any] | None
    quirurgico: dict[str, Any] | None
    factura: dict[str, Any] | None
    radiologia_docs: list[dict[str, Any]]
    laboratorio_docs: list[dict[str, Any]]
    generico_docs: list[dict[str, Any]]


def _normalizar_cie10(entries, retriever=None):
    return normalize_cie10_entries(entries, retriever=retriever)


def _strip_html_tags(value: str) -> str:
    return re.sub(r"<[^>]+>", "", value or "").strip()


def _get_epicrisis_pdf_cache_collection(mongo_storage):
    return mongo_storage.collection.database[EPICRISIS_PDF_CACHE_COLLECTION]


def _sanitize_legacy_ayudas_diagnosticas_pdf(html_pdf: str) -> str:
    if not html_pdf:
        return html_pdf

    section_pattern = re.compile(
        r'(<section class="pdf-section">\s*<h2>6\. Ayudas Diagnósticas</h2>\s*<table>)(.*?)(</table>)',
        flags=re.DOTALL,
    )

    def _strip_legacy_fuente_column(match: re.Match[str]) -> str:
        table_head, table_content, table_tail = match.groups()
        sanitized_content = re.sub(r"\s*<th>\s*Fuente\s*</th>", "", table_content, flags=re.IGNORECASE)

        def _sanitize_row(row_match: re.Match[str]) -> str:
            row_html = row_match.group(0).replace('colspan="5"', 'colspan="4"')
            cells = list(re.finditer(r"<td\b[^>]*>.*?</td>", row_html, flags=re.DOTALL))
            if len(cells) != 5:
                return row_html
            last_cell = cells[-1]
            return f"{row_html[: last_cell.start()]}{row_html[last_cell.end() :]}"

        sanitized_content = re.sub(r"<tr>.*?</tr>", _sanitize_row, sanitized_content, flags=re.DOTALL)
        return f"{table_head}{sanitized_content}{table_tail}"

    sanitized_html = section_pattern.sub(_strip_legacy_fuente_column, html_pdf, count=1)
    medication_section_pattern = re.compile(
        r'(<section class="pdf-section">\s*<h2>\d+\. )Medicamentos del caso(</h2>\s*<table>)(.*?)(</table>)',
        flags=re.DOTALL | re.IGNORECASE,
    )

    def _strip_legacy_medication_status(match: re.Match[str]) -> str:
        section_head, table_head, table_content, table_tail = match.groups()
        sanitized_content = re.sub(r"\s*<th>\s*Estado\s*</th>", "", table_content, flags=re.IGNORECASE)

        def _sanitize_row(row_match: re.Match[str]) -> str:
            row_html = row_match.group(0).replace('colspan="5"', 'colspan="4"')
            cells = list(re.finditer(r"<td\b[^>]*>.*?</td>", row_html, flags=re.DOTALL))
            if len(cells) != 5:
                return row_html
            last_cell = cells[-1]
            return f"{row_html[: last_cell.start()]}{row_html[last_cell.end() :]}"

        sanitized_content = re.sub(r"<tr>.*?</tr>", _sanitize_row, sanitized_content, flags=re.DOTALL)
        return f"{section_head}Medicamentos{table_head}{sanitized_content}{table_tail}"

    return medication_section_pattern.sub(_strip_legacy_medication_status, sanitized_html, count=1)


def _inject_print_script(html_pdf: str) -> str:
    sanitized_html = _sanitize_legacy_ayudas_diagnosticas_pdf(html_pdf)
    script = '<script>window.addEventListener("load", function () { window.print(); });</script>'
    lower_html = sanitized_html.lower()
    body_close_index = lower_html.rfind("</body>")
    if body_close_index >= 0:
        return f"{sanitized_html[:body_close_index]}{script}{sanitized_html[body_close_index:]}"
    return f"{sanitized_html}{script}"


def _canonical_case_epicrisis_url(case_key: str) -> str:
    return f"/epicrisis?case_key={case_key}"


def _legacy_document_epicrisis_url(documento_id: str) -> str:
    return f"/epicrisis?documento_id={documento_id}"


def _build_epicrisis_regen_url(*, case_key: str | None = None, documento_id: str | None = None) -> str | None:
    normalized_case_key = str(case_key or "").strip()
    if normalized_case_key:
        return f"{_canonical_case_epicrisis_url(normalized_case_key)}&regen=1"

    normalized_documento_id = str(documento_id or "").strip()
    if normalized_documento_id:
        return f"{_legacy_document_epicrisis_url(normalized_documento_id)}&regen=1"

    return None


def _prepare_epicrisis_template_context(
    context: dict[str, Any],
    *,
    request: Request,
    current_user: CurrentUser,
    regen_url: str | None,
    regen_case_key: str,
    epicrisis_cached: bool,
    cie10_retriever: Any,
) -> dict[str, Any]:
    prepared = refresh_diagnostico_context(
        normalize_context_payload(context),
        retriever=cie10_retriever,
    )
    prepared["factura"] = _normalize_factura_medicamentos(prepared.get("factura"))
    prepared["medicamentos_hc"] = normalizar_lista_medicamentos(
        prepared.get("medicamentos_hc"),
        fuente="historia_clinica",
    )
    prepared["medicamentos_hc_display"] = prepared.get(
        "medicamentos_hc_display"
    ) or _build_medication_display_list(prepared.get("medicamentos_hc"))
    prepared["antecedentes_hc"] = prepared.get("antecedentes_hc") or []
    antecedentes_estructurados = prepared.get("antecedentes_hc_estructurados") or []
    if not antecedentes_estructurados and prepared.get("historia"):
        antecedentes_estructurados = extraer_antecedentes_historia_estructurados(prepared["historia"])
    if not antecedentes_estructurados:
        antecedentes_estructurados = [
            {
                "categoria": "otro",
                "descripcion": item,
                "estado": "presente",
                "discordante": False,
            }
            for item in prepared["antecedentes_hc"]
            if str(item or "").strip()
        ]
    prepared["antecedentes_hc_estructurados"] = antecedentes_estructurados
    prepared["antecedentes_hc_view"] = group_antecedentes_for_view(antecedentes_estructurados)
    prepared["recomendaciones_medicas"] = prepared.get("recomendaciones_medicas") or []
    prepared["recomendaciones_medicas_view"] = group_recomendaciones_medicas_for_view(
        prepared["recomendaciones_medicas"]
    )
    prepared["procedimientos_hc"] = prepared.get("procedimientos_hc") or []
    prepared["procedimientos_factura"] = prepared.get("procedimientos_factura") or []
    prepared["diagnosticos_consolidados"] = prepared.get("diagnosticos_consolidados") or []
    prepared["ayudas_diagnosticas"] = prepared.get("ayudas_diagnosticas") or []
    prepared["imagenes_diagnosticas"] = prepared.get("imagenes_diagnosticas") or []
    prepared["request"] = request
    prepared["user"] = current_user
    prepared["regen_url"] = regen_url
    prepared["regen_case_key"] = regen_case_key
    prepared["epicrisis_cached"] = epicrisis_cached
    prepared.update(build_shell_context(request=request, user=current_user))
    return prepared


def _sanitize_epicrisis_error_detail(detail: Any) -> str | None:
    text = re.sub(r"\s+", " ", _strip_html_tags(str(detail or ""))).strip()
    if not text:
        return None
    if len(text) > 280:
        return f"{text[:277].rstrip()}..."
    return text


def _normalize_string_list(value: Any) -> list[str]:
    if not isinstance(value, list):
        return []
    return [text for item in value if (text := str(item).strip())]


def _normalize_epicrisis_rule_findings(value: Any) -> list[dict[str, Any]]:
    if not isinstance(value, list):
        return []

    findings: list[dict[str, Any]] = []
    for item in value:
        if not isinstance(item, dict):
            continue
        findings.append(
            {
                "rule_code": str(item.get("rule_code") or "").strip(),
                "severity": str(item.get("severity") or "").strip(),
                "message": _sanitize_epicrisis_error_detail(item.get("message")) or "",
                "blocking": bool(item.get("blocking")),
                "checked_documents": _normalize_string_list(item.get("document_types_checked")),
            }
        )
    return findings


def _resolve_epicrisis_action_hint(
    *,
    rule_code: str,
    missing_documents: list[str],
    message: str,
) -> str:
    normalized_rule_code = rule_code.strip().upper()
    normalized_missing = {item.strip().lower() for item in missing_documents}
    normalized_message = message.strip().lower()

    if (
        normalized_rule_code == "EPICRISIS-HC-001"
        or "historia_clinica" in normalized_missing
        or "historia clínica" in normalized_message
    ):
        return "Adjunta una historia clínica al caso para poder generar la epicrisis."

    if missing_documents:
        suffix = ", ".join(missing_documents)
        return f"Adjunta los documentos faltantes ({suffix}) y vuelve a intentar."

    return "Revisa los documentos asociados al caso y vuelve a intentar."


def _build_epicrisis_rule_diagnostic(status_payload: dict[str, Any] | None) -> dict[str, Any] | None:
    payload = status_payload if isinstance(status_payload, dict) else {}
    findings = _normalize_epicrisis_rule_findings(payload.get("epicrisis_rule_findings"))
    blocking_findings = [item for item in findings if item.get("blocking")]
    selected_findings = blocking_findings or findings
    missing_documents = _normalize_string_list(payload.get("epicrisis_missing_documents"))
    fallback_reason = _sanitize_epicrisis_error_detail(
        payload.get("epicrisis_blocking_reason") or payload.get("epicrisis_error")
    )

    checked_documents: list[str] = []
    for finding in selected_findings:
        for item in finding.get("checked_documents", []):
            if item not in checked_documents:
                checked_documents.append(item)

    primary_finding = selected_findings[0] if selected_findings else {}
    rule_code = str(primary_finding.get("rule_code") or "").strip()
    message = str(primary_finding.get("message") or "").strip() or fallback_reason or ""
    action_hint = _resolve_epicrisis_action_hint(
        rule_code=rule_code,
        missing_documents=missing_documents,
        message=message or fallback_reason or "",
    )

    parts: list[str] = []
    if (
        rule_code.upper() == "EPICRISIS-HC-001"
        or "historia_clinica" in {item.lower() for item in missing_documents}
        or "historia clínica" in message.lower()
    ):
        parts.append("No se puede generar la epicrisis porque falta la historia clínica.")
    elif rule_code:
        parts.append(f"La regla {rule_code} bloqueó la generación.")
    elif message:
        parts.append(message)
    if missing_documents and "historia_clinica" not in {item.lower() for item in missing_documents}:
        parts.append(f"Falta: {', '.join(missing_documents)}.")
    if checked_documents:
        parts.append(f"Documentos detectados en el caso: {', '.join(checked_documents)}.")
    if action_hint:
        parts.append(action_hint)
    if not parts and fallback_reason:
        parts.append(fallback_reason)

    if not parts and not selected_findings:
        return None

    return {
        "summary": " ".join(part for part in parts if part).strip(),
        "findings": selected_findings,
        "missing_documents": missing_documents,
        "checked_documents": checked_documents,
        "blocking_reason": fallback_reason,
        "action_hint": action_hint,
    }


def _build_epicrisis_blocking_detail(status_payload: dict[str, Any] | None) -> str | None:
    diagnostic = _build_epicrisis_rule_diagnostic(status_payload)
    if diagnostic and diagnostic.get("summary"):
        return str(diagnostic["summary"]).strip()

    payload = status_payload if isinstance(status_payload, dict) else {}
    blocking_reason = _sanitize_epicrisis_error_detail(
        payload.get("epicrisis_blocking_reason") or payload.get("epicrisis_error")
    )
    missing_documents = _normalize_string_list(payload.get("epicrisis_missing_documents"))
    if not blocking_reason and not missing_documents:
        return None
    if missing_documents:
        suffix = ", ".join(missing_documents)
        if blocking_reason:
            return f"{blocking_reason} Documentos faltantes: {suffix}."
        return f"Documentos faltantes: {suffix}."
    return blocking_reason


def _epicrisis_secondary_action(case_key: str | None) -> tuple[str, str]:
    normalized_case_key = str(case_key or "").strip()
    if normalized_case_key:
        return "/casos", "Volver a casos"
    return "/dashboard", "Volver al dashboard"


def _render_epicrisis_status_view(
    *,
    request: Request,
    templates,
    current_user: CurrentUser,
    status_variant: str,
    title: str,
    case_key: str,
    case_number: str,
    message: str,
    detail: str | None,
    primary_action_href: str,
    primary_action_label: str,
    status_code: int,
    rule_diagnostic: dict[str, Any] | None = None,
):
    secondary_action_href, secondary_action_label = _epicrisis_secondary_action(case_key)
    return templates.TemplateResponse(
        request,
        "epicrisis_status.html",
        {
            "request": request,
            "user": current_user,
            "title": title,
            "status_variant": status_variant,
            "case_key": case_key,
            "case_number": case_number,
            "message": message,
            "detail": detail,
            "rule_diagnostic": rule_diagnostic or {},
            "primary_action_href": primary_action_href,
            "primary_action_label": primary_action_label,
            "secondary_action_href": secondary_action_href,
            "secondary_action_label": secondary_action_label,
            **build_shell_context(request=request, user=current_user),
        },
        status_code=status_code,
    )


def _apply_legacy_epicrisis_deprecation_headers(response):
    response.headers["Deprecation"] = "true"
    response.headers["Warning"] = LEGACY_EPICRISIS_ID_WARNING
    return response


def _build_medication_display_list(items) -> list[str]:
    return [formatear_medicamento_canonico(item) for item in normalizar_lista_medicamentos(items)]


def _normalize_factura_medicamentos(doc: dict[str, Any] | None) -> dict[str, Any] | None:
    if not isinstance(doc, dict):
        return doc
    factura_json = doc.get("factura_json")
    if not isinstance(factura_json, dict):
        return doc
    servicios = factura_json.get("servicios_procedimientos")
    if not isinstance(servicios, dict):
        return doc
    servicios["medicamentos"] = normalizar_lista_medicamentos(servicios.get("medicamentos"), fuente="factura")
    return doc


def _build_factura_procedimiento(codigo: Any, descripcion: Any, fallback: Any = "") -> dict[str, str] | None:
    codigo_final = str(codigo or "").strip()
    descripcion_final = str(descripcion or "").strip() or str(fallback or "").strip()
    if not is_valid_factura_procedimiento(codigo_final, descripcion_final):
        return None
    return {
        "codigo_soat": codigo_final,
        "descripcion": descripcion_final,
    }


def _extraer_procedimientos_factura_json(factura_doc: dict[str, Any]) -> list[dict[str, str]]:
    factura_json = factura_doc.get("factura_json")
    if not isinstance(factura_json, dict):
        return []

    servicios = factura_json.get("servicios_procedimientos") or {}
    procedimientos: list[dict[str, str]] = []
    for item in servicios.get("procedimientos_quirurgicos") or []:
        if not isinstance(item, dict):
            continue
        procedimiento = _build_factura_procedimiento(
            item.get("codigo_cups"),
            item.get("descripcion"),
            item.get("concepto"),
        )
        if procedimiento:
            procedimientos.append(procedimiento)
    return procedimientos


def _extraer_procedimientos_factura_html(factura_doc: dict[str, Any]) -> list[dict[str, str]]:
    factura_html = str(factura_doc.get("analisis_html") or render_document_analysis_html(factura_doc) or "")
    if not factura_html:
        return []
    return extract_factura_procedimientos_from_html(factura_html)


def _extraer_procedimientos_factura(factura_doc: dict[str, Any] | None) -> list[dict[str, str]]:
    if not isinstance(factura_doc, dict):
        return []

    procedimientos = _extraer_procedimientos_factura_json(factura_doc)
    if procedimientos:
        return procedimientos

    return _extraer_procedimientos_factura_html(factura_doc)


def _buscar_radiologia_por_paciente(
    mongo, username: str, nombre_paciente: str | None
) -> list[dict[str, Any]]:
    return _buscar_documentos_por_paciente(mongo, username, nombre_paciente, "radiologia")


def _buscar_documentos_por_paciente(
    mongo,
    username: str,
    nombre_paciente: str | None,
    tipo_documento: str,
) -> list[dict[str, Any]]:
    nombre = str(nombre_paciente or "").strip()
    if not nombre or nombre == "desconocido":
        return []
    return list(
        mongo.collection.find(
            {
                "usuario": username,
                "nombre_paciente": nombre,
                "tipo_documento": tipo_documento,
            },
            sort=[("fecha_analisis", -1)],
        )
    )


def _merge_codigos_soat(existing: list[dict[str, str]], nuevos: list[dict[str, str]]) -> list[dict[str, str]]:
    merged: list[dict[str, str]] = []
    seen = set()
    for item in (existing or []) + (nuevos or []):
        if not isinstance(item, dict):
            continue
        key = (
            str(item.get("codigo_soat") or "").strip().lower(),
            str(item.get("descripcion") or "").strip().lower(),
        )
        if key in seen:
            continue
        seen.add(key)
        merged.append(item)
    return merged


def _generar_codigos_desde_soat_services(
    services: AppServices,
    descripciones: list[dict[str, str]],
    *,
    username: str = "",
) -> dict[str, list[dict]]:
    return {
        "resultados": generar_codigos_desde_soat_con_gemini(
            descripciones,
            client_gemini=services.client_gemini,
            llm_router=services.llm_router,
            cache_repository=services.llm_task_cache_repository,
            username=username,
        )
    }


def _serialize_epicrisis_doc(
    doc: dict[str, Any] | None,
    *,
    cie10_retriever: Any,
) -> dict[str, Any] | None:
    serialized = serialize_clinical_doc(doc)
    if serialized and "codigos_cie10" in serialized:
        serialized["codigos_cie10"] = _normalizar_cie10(
            serialized["codigos_cie10"], retriever=cie10_retriever
        )
    return serialized


def _has_known_patient_name(nombre_paciente: str | None) -> bool:
    return bool(nombre_paciente and nombre_paciente != "desconocido")


def _find_patient_document_by_type(
    mongo,
    username: str,
    nombre_paciente: str | None,
    *,
    tipo_documento: str,
    allow_missing_type_fallback: bool = False,
) -> dict[str, Any] | None:
    if not _has_known_patient_name(nombre_paciente):
        return None

    document = mongo.collection.find_one(
        {
            "usuario": username,
            "nombre_paciente": nombre_paciente,
            "tipo_documento": tipo_documento,
        },
        sort=[("fecha_analisis", -1)],
    )
    if document or not allow_missing_type_fallback:
        return document

    return mongo.collection.find_one(
        {
            "usuario": username,
            "nombre_paciente": nombre_paciente,
            "tipo_documento": {"$exists": False},
        },
        sort=[("fecha_analisis", -1)],
    )


def _build_epicrisis_document_bundle(
    mongo,
    username: str,
    nombre_paciente: str | None,
    *,
    base_doc: dict[str, Any] | None = None,
) -> EpicrisisDocumentBundle:
    document_type = str((base_doc or {}).get("tipo_documento") or "").strip()
    historia = base_doc if document_type in {"", "historia_clinica"} else None
    quirurgico = base_doc if document_type == "quirurgico" else None
    factura = base_doc if document_type == "factura" else None

    historia = historia or _find_patient_document_by_type(
        mongo,
        username,
        nombre_paciente,
        tipo_documento="historia_clinica",
        allow_missing_type_fallback=True,
    )
    quirurgico = quirurgico or _find_patient_document_by_type(
        mongo,
        username,
        nombre_paciente,
        tipo_documento="quirurgico",
    )
    factura = factura or _find_patient_document_by_type(
        mongo,
        username,
        nombre_paciente,
        tipo_documento="factura",
    )

    return EpicrisisDocumentBundle(
        nombre_paciente=str(nombre_paciente or "").strip() or "desconocido",
        historia=historia,
        quirurgico=quirurgico,
        factura=factura,
        radiologia_docs=_buscar_radiologia_por_paciente(mongo, username, nombre_paciente),
        laboratorio_docs=_buscar_documentos_por_paciente(mongo, username, nombre_paciente, "laboratorio"),
        generico_docs=_buscar_documentos_por_paciente(mongo, username, nombre_paciente, "generico"),
    )


def _serialize_epicrisis_bundle(
    bundle: EpicrisisDocumentBundle,
    *,
    cie10_retriever: Any,
) -> dict[str, Any]:
    return {
        "historia": _serialize_epicrisis_doc(bundle.historia, cie10_retriever=cie10_retriever),
        "quirurgico": _serialize_epicrisis_doc(bundle.quirurgico, cie10_retriever=cie10_retriever),
        "factura": _normalize_factura_medicamentos(
            _serialize_epicrisis_doc(bundle.factura, cie10_retriever=cie10_retriever)
        ),
        "radiologia": [
            _serialize_epicrisis_doc(doc, cie10_retriever=cie10_retriever) for doc in bundle.radiologia_docs
        ],
        "laboratorio": [
            _serialize_epicrisis_doc(doc, cie10_retriever=cie10_retriever) for doc in bundle.laboratorio_docs
        ],
        "generico": [
            _serialize_epicrisis_doc(doc, cie10_retriever=cie10_retriever) for doc in bundle.generico_docs
        ],
    }


def _document_analysis_html(serialized_doc: dict[str, Any] | None) -> str:
    if not isinstance(serialized_doc, dict):
        return ""
    return str(serialized_doc.get("analisis_html") or "")


def _build_soat_context_from_serialized(
    *,
    services: AppServices,
    serialized_docs: dict[str, Any],
) -> tuple[list[dict[str, Any]], str]:
    return _construir_soat_y_glosa(
        historia_html=_document_analysis_html(serialized_docs.get("historia")),
        qx_html=_document_analysis_html(serialized_docs.get("quirurgico")),
        soat_retriever=services.soat_retriever,
        client_groq=services.client_groq,
        llm_router=services.llm_router,
    )


def _generate_codes_from_source(
    *,
    services: AppServices,
    entries: list[dict[str, str]],
    username: str,
    warning_message: str,
) -> list[dict[str, str]]:
    if not entries:
        return []
    try:
        result = _generar_codigos_desde_soat_services(
            services,
            entries,
            username=username,
        )
    except Exception as exc:
        logger.warning(warning_message, exc)
        return []
    return result.get("resultados", [])


def _build_codigos_desde_soat(
    *,
    services: AppServices,
    soat_resultados: list[dict[str, Any]],
    factura: dict[str, Any] | None,
    username: str,
) -> list[dict[str, str]]:
    descripcion_entries = [
        {
            "codigo_soat": item.get("codigo_soat", ""),
            "descripcion": item.get("descripcion", ""),
        }
        for item in soat_resultados[:5]
    ]
    codigos = _generate_codes_from_source(
        services=services,
        entries=descripcion_entries,
        username=username,
        warning_message="Error generando códigos desde SOAT con Gemini: %s",
    )
    procedimientos_factura = _extraer_procedimientos_factura(factura)
    if not procedimientos_factura:
        return codigos
    return _merge_codigos_soat(
        codigos,
        _generate_codes_from_source(
            services=services,
            entries=procedimientos_factura,
            username=username,
            warning_message="Error procesando procedimientos de factura automáticamente: %s",
        ),
    )


def _build_ayudas_context(
    bundle: EpicrisisDocumentBundle,
    hallazgos_qx: list[dict[str, Any]],
    descripcion_qx: str | None,
) -> list[dict[str, Any]]:
    return build_ayudas_diagnosticas(
        {
            "factura": bundle.factura,
            "radiologia": bundle.radiologia_docs,
            "laboratorio": bundle.laboratorio_docs,
            "generico": bundle.generico_docs,
            "historia": bundle.historia,
            "quirurgico": bundle.quirurgico,
            "hallazgos_quirurgicos": hallazgos_qx,
            "descripcion_procedimiento": descripcion_qx,
        }
    )


def _build_epicrisis_context_payload(
    *,
    bundle: EpicrisisDocumentBundle,
    serialized_docs: dict[str, Any],
    soat_resultados: list[dict[str, Any]],
    glosa_html: str,
    codigos_desde_soat: list[dict[str, str]],
    regen_url: str | None,
    regen_case_key: str,
    epicrisis_cached: bool,
) -> dict[str, Any]:
    metadatos_hc = extraer_metadatos_historia(bundle.historia)
    antecedentes_hc = extraer_antecedentes_historia(bundle.historia)
    antecedentes_hc_estructurados = extraer_antecedentes_historia_estructurados(bundle.historia)
    procedimientos_hc = extraer_procedimientos_historia(bundle.historia)
    medicamentos_hc = extraer_medicamentos_historia(bundle.historia)
    medicamentos_hc_display = _build_medication_display_list(medicamentos_hc)
    recomendaciones_medicas = extract_recomendaciones_medicas(bundle.historia)
    hallazgos_qx, descripcion_qx = extraer_secciones_quirurgicas(bundle.quirurgico)
    ayudas_diagnosticas = _build_ayudas_context(bundle, hallazgos_qx, descripcion_qx)
    imagenes_diagnosticas = [
        format_ayuda_diagnostica_presentacion(item)
        for item in ayudas_diagnosticas
        if item.get("tipo") == "imagen"
    ] or extraer_imagenes_diagnosticas(bundle.factura, bundle.radiologia_docs)

    return {
        "nombre_paciente": bundle.nombre_paciente,
        "historia": serialized_docs["historia"],
        "quirurgico": serialized_docs["quirurgico"],
        "factura": serialized_docs["factura"],
        "radiologia": serialized_docs["radiologia"],
        "laboratorio": serialized_docs["laboratorio"],
        "generico": serialized_docs["generico"],
        "metadatos_hc": metadatos_hc,
        "antecedentes_hc": antecedentes_hc,
        "antecedentes_hc_estructurados": antecedentes_hc_estructurados,
        "procedimientos_hc": procedimientos_hc,
        "medicamentos_hc": medicamentos_hc,
        "medicamentos_hc_display": medicamentos_hc_display,
        "recomendaciones_medicas": recomendaciones_medicas,
        "ayudas_diagnosticas": ayudas_diagnosticas,
        "imagenes_diagnosticas": imagenes_diagnosticas,
        "hallazgos_quirurgicos": hallazgos_qx,
        "descripcion_procedimiento": descripcion_qx,
        "soat_resultados": soat_resultados,
        "glosa_analisis": glosa_html,
        "codigos_desde_soat": codigos_desde_soat,
        "regen_url": regen_url,
        "regen_case_key": regen_case_key,
        "epicrisis_cached": epicrisis_cached,
        "orden_costo_version": COST_ORDER_VERSION,
    }


def _build_full_epicrisis_context(
    *,
    services: AppServices,
    bundle: EpicrisisDocumentBundle,
    username: str,
    cie10_retriever: Any,
    regen_url: str | None,
    regen_case_key: str,
    epicrisis_cached: bool,
    request: Request,
    current_user: CurrentUser,
) -> dict[str, Any]:
    serialized_docs = _serialize_epicrisis_bundle(bundle, cie10_retriever=cie10_retriever)
    soat_resultados, glosa_html = _build_soat_context_from_serialized(
        services=services, serialized_docs=serialized_docs
    )
    codigos_desde_soat = _build_codigos_desde_soat(
        services=services,
        soat_resultados=soat_resultados,
        factura=bundle.factura,
        username=username,
    )
    context = _build_epicrisis_context_payload(
        bundle=bundle,
        serialized_docs=serialized_docs,
        soat_resultados=soat_resultados,
        glosa_html=glosa_html,
        codigos_desde_soat=codigos_desde_soat,
        regen_url=regen_url,
        regen_case_key=regen_case_key,
        epicrisis_cached=epicrisis_cached,
    )
    integrated_summary = build_integrated_summary_context(
        username=username,
        historia=bundle.historia,
        quirurgico=bundle.quirurgico,
        metadatos_hc=context["metadatos_hc"],
        summary_composer=services.case_epicrisis_service.summary_composer,
    )
    context["resumen_clinico_integrado"] = integrated_summary
    context["metadatos_hc"] = dict(context["metadatos_hc"])
    context["metadatos_hc"]["resumen"] = integrated_summary["texto"]
    return _prepare_epicrisis_template_context(
        context,
        request=request,
        current_user=current_user,
        regen_url=regen_url,
        regen_case_key=regen_case_key,
        epicrisis_cached=epicrisis_cached,
        cie10_retriever=cie10_retriever,
    )


def _document_ref_id(doc: dict[str, Any] | None) -> str | None:
    if not doc or not doc.get("_id"):
        return None
    return str(doc["_id"])


def _find_legacy_cached_epicrisis(
    mongo,
    *,
    username: str,
    resolved_document_id: str,
    bundle: EpicrisisDocumentBundle,
) -> dict[str, Any] | None:
    return mongo.collection.find_one(
        {
            "usuario": username,
            "tipo_documento": "epicrisis",
            "epicrisis_base_id": resolved_document_id,
            "historia_id": _document_ref_id(bundle.historia),
            "quirurgico_id": _document_ref_id(bundle.quirurgico),
            "factura_id": _document_ref_id(bundle.factura),
        },
        sort=[("fecha_analisis", -1)],
    )


def _render_legacy_cached_epicrisis(
    *,
    request: Request,
    current_user: CurrentUser,
    templates,
    cie10_retriever: Any,
    cache_doc: dict[str, Any],
    bundle: EpicrisisDocumentBundle,
    regen_url: str | None,
    services: AppServices | None = None,
) -> Any:
    context = _prepare_epicrisis_template_context(
        cache_doc["contexto"],
        request=request,
        current_user=current_user,
        regen_url=regen_url,
        regen_case_key="",
        epicrisis_cached=True,
        cie10_retriever=cie10_retriever,
    )
    if services is not None:
        cache_payload = cache_doc.get("contexto") or {}
        historia_case_key = str((bundle.historia or {}).get("case_key") or "")
        context = get_demo_identity_service(services).project_epicrisis_context(
            username=current_user.username,
            context=context,
            case_key=str(cache_doc.get("case_key") or historia_case_key),
            case_number=str(cache_payload.get("case_number") or ""),
            patient_id=str(cache_payload.get("patient_id") or ""),
            patient_name=str(cache_payload.get("nombre_paciente") or bundle.nombre_paciente),
        )
    if not context.get("radiologia"):
        context["radiologia"] = [
            _serialize_epicrisis_doc(doc, cie10_retriever=cie10_retriever) for doc in bundle.radiologia_docs
        ]
    if not context.get("laboratorio"):
        context["laboratorio"] = [
            _serialize_epicrisis_doc(doc, cie10_retriever=cie10_retriever) for doc in bundle.laboratorio_docs
        ]
    if not context.get("generico"):
        context["generico"] = [
            _serialize_epicrisis_doc(doc, cie10_retriever=cie10_retriever) for doc in bundle.generico_docs
        ]
    context["ayudas_diagnosticas"] = context.get("ayudas_diagnosticas") or build_ayudas_diagnosticas(context)
    context["imagenes_diagnosticas"] = context.get("imagenes_diagnosticas") or extraer_imagenes_diagnosticas(
        context.get("factura"),
        bundle.radiologia_docs,
    )
    return _apply_legacy_epicrisis_deprecation_headers(
        templates.TemplateResponse(request, "epicrisis.html", context)
    )


def _cache_legacy_epicrisis_context(
    mongo,
    *,
    username: str,
    resolved_document_id: str,
    bundle: EpicrisisDocumentBundle,
    contexto: dict[str, Any],
    regen: bool,
) -> None:
    try:
        mongo.collection.insert_one(
            {
                "usuario": username,
                "tipo_documento": "epicrisis",
                "epicrisis_base_id": resolved_document_id,
                "nombre_paciente": bundle.nombre_paciente,
                "historia_id": _document_ref_id(bundle.historia),
                "quirurgico_id": _document_ref_id(bundle.quirurgico),
                "factura_id": _document_ref_id(bundle.factura),
                "fecha_analisis": datetime.now(),
                "regen_requested": bool(regen),
                "contexto": {
                    "nombre_paciente": bundle.nombre_paciente,
                    "historia": contexto["historia"],
                    "quirurgico": contexto["quirurgico"],
                    "factura": contexto["factura"],
                    "radiologia": contexto["radiologia"],
                    "laboratorio": contexto["laboratorio"],
                    "generico": contexto["generico"],
                    "metadatos_hc": contexto["metadatos_hc"],
                    "antecedentes_hc": contexto.get("antecedentes_hc", []),
                    "antecedentes_hc_estructurados": contexto.get("antecedentes_hc_estructurados", []),
                    "procedimientos_hc": contexto["procedimientos_hc"],
                    "medicamentos_hc": contexto["medicamentos_hc"],
                    "medicamentos_hc_display": contexto["medicamentos_hc_display"],
                    "recomendaciones_medicas": contexto.get("recomendaciones_medicas", []),
                    "ayudas_diagnosticas": contexto["ayudas_diagnosticas"],
                    "imagenes_diagnosticas": contexto["imagenes_diagnosticas"],
                    "hallazgos_quirurgicos": contexto["hallazgos_quirurgicos"],
                    "descripcion_procedimiento": contexto["descripcion_procedimiento"],
                    "soat_resultados": contexto["soat_resultados"],
                    "glosa_analisis": contexto["glosa_analisis"],
                    "codigos_desde_soat": contexto["codigos_desde_soat"],
                    "regen_url": contexto["regen_url"],
                    "regen_case_key": "",
                    "epicrisis_cached": False,
                },
            }
        )
    except Exception as cache_error:
        logger.warning("Error guardando cache de epicrisis: %s", cache_error)
