from __future__ import annotations

from typing import Any

from app.case_epicrisis.application.cost_ordering import (
    COST_ORDER_VERSION,
    apply_factura_cost_ordering,
)
from app.case_epicrisis.application.curation import (
    apply_curation_pop_metadata,
    curation_context_payload,
    curation_from_context,
)
from app.case_epicrisis.application.medication_pertinence import (
    MedicationInvoiceState,
    apply_clinical_pertinence_reviews,
    build_medication_glosa_findings,
    evaluate_medication_pertinence,
)
from app.case_epicrisis.application.publication import (
    build_audit_review_items,
    build_publication_projections,
    publication_summary,
)
from app.case_epicrisis.domain.models import (
    RecomendacionMedica,
)
from app.services.clinical_document_projection import (
    render_document_analysis_html,
)
from app.services.clinical_processing import (
    extraer_antecedentes_historia,
    extraer_medicamentos_historia,
    extraer_procedimientos_historia,
    normalizar_lista_medicamentos,
)
from app.services.factura_html_legacy import (
    is_valid_factura_procedimiento,
)
from app.services.historia_medicamentos import reconcile_case_medications

from .cie10 import (
    _coerce_diagnostico_catalog,
    _coerce_diagnostico_hallazgos,
    build_diagnosticos_consolidados,
    normalize_cie10_entries,
)
from .common import (
    _clean_unique_text_list,
    _normalize_whitespace,
    strip_html_tags,
)
from .diagnostic_aids import (
    build_ayudas_diagnosticas,
    build_diagnostic_aid_quality_report,
    classify_diagnostic_aid_candidates,
    coerce_ayudas_diagnosticas,
    evaluar_ayuda_diagnostica_exportable,
    format_ayuda_diagnostica_presentacion,
    merge_soat_results,
)
from .factura import (
    _servicios_factura,
    extraer_procedimientos_factura,
    normalize_factura_medicamentos,
)
from .medications import (
    build_medication_display_list,
)
from .pdf_draft import (
    _normalize_manual_soat_results,
    coerce_pdf_draft,
    coerce_pdf_preparation_user_metadata,
)
from .pdf_exports import (
    build_pdf_chronological_export_blocks,
    build_pdf_export_blocks,
    build_pdf_preparation_procedimientos,
    reconcile_pdf_draft,
)


def refresh_diagnostico_context(
    context: dict[str, Any],
    *,
    retriever: Any = None,
) -> dict[str, Any]:
    refreshed = dict(context or {})
    for doc_key, source in (("historia", "historia_clinica"), ("quirurgico", "quirurgico")):
        doc = refreshed.get(doc_key)
        if not isinstance(doc, dict):
            continue
        doc_copy = dict(doc)
        doc_copy["codigos_cie10"] = normalize_cie10_entries(
            doc_copy.get("codigos_cie10"),
            retriever=retriever,
            source=source,
        )
        refreshed[doc_key] = doc_copy

    refreshed["pdf_draft"] = coerce_pdf_draft(refreshed.get("pdf_draft"))
    refreshed.update(build_diagnosticos_consolidados(refreshed, retriever=retriever))
    refreshed["ayudas_diagnosticas"] = build_ayudas_diagnosticas(refreshed)
    refreshed["diagnostic_aid_quality"] = build_diagnostic_aid_quality_report(
        refreshed, refreshed["ayudas_diagnosticas"]
    )
    refreshed["imagenes_diagnosticas"] = [
        format_ayuda_diagnostica_presentacion(item)
        for item in refreshed["ayudas_diagnosticas"]
        if item.get("tipo") == "imagen"
    ]
    refreshed["pdf_draft"] = reconcile_pdf_draft(refreshed.get("pdf_draft"), refreshed)
    excluded_ayudas = refreshed["pdf_draft"].get("excluded_ayudas_diagnosticas_keys")
    for ayuda in refreshed["ayudas_diagnosticas"]:
        ayuda["exportable"] = evaluar_ayuda_diagnostica_exportable(ayuda, excluded_ayudas)
    refreshed.update(build_pdf_export_blocks(refreshed))
    if curation := curation_from_context(refreshed):
        refreshed.update(
            curation_context_payload(
                curation,
                pdf_preparation_overrides=refreshed.get("pdf_draft"),
                diagnostic_aids=refreshed.get("ayudas_diagnosticas"),
                source_context=refreshed,
            )
        )
        apply_curation_pop_metadata(refreshed, curation)
    refreshed["audit_review_items"] = build_audit_review_items(refreshed)
    refreshed["publication_summary"] = publication_summary(refreshed)
    refreshed.update(build_pdf_chronological_export_blocks(refreshed))
    refreshed["publication_projections"] = build_publication_projections(refreshed)
    return refreshed


def _normalize_optional_doc(value: Any) -> dict[str, Any] | None:
    return dict(value) if isinstance(value, dict) else None


def _normalize_dict_list(value: Any) -> list[dict[str, Any]]:
    return [item for item in (value or []) if isinstance(item, dict)]


def _normalize_text_list(value: Any) -> list[str]:
    return _clean_unique_text_list(value)


def _normalize_recomendaciones_medicas(value: Any) -> list[dict[str, Any]]:
    result: list[dict[str, Any]] = []
    seen: set[tuple[str, str, str]] = set()
    for raw in value if isinstance(value, list) else []:
        if not isinstance(raw, dict):
            continue
        try:
            item = RecomendacionMedica.model_validate(raw)
        except ValueError:
            continue
        key = (item.categoria.value, item.indicacion.casefold(), item.duracion.casefold())
        if key in seen:
            continue
        seen.add(key)
        result.append(item.model_dump(mode="json", exclude_none=True))
    return result


def _normalize_historia_clinica_base(normalized: dict[str, Any]) -> None:
    historia = normalized.get("historia")
    historia_html = str(
        (historia or {}).get("analisis_html") or render_document_analysis_html(historia) or ""
    ).strip()

    antecedentes_hc = normalized.get("antecedentes_hc")
    if isinstance(antecedentes_hc, list):
        normalized["antecedentes_hc"] = _normalize_text_list(antecedentes_hc)
    else:
        normalized["antecedentes_hc"] = []
    if historia_html and not normalized["antecedentes_hc"]:
        normalized["antecedentes_hc"] = _normalize_text_list(
            extraer_antecedentes_historia(historia or historia_html)
        )
    if normalized["antecedentes_hc"]:
        normalized["antecedentes"] = normalized["antecedentes_hc"]

    antecedentes_estructurados = normalized.get("antecedentes_hc_estructurados")
    normalized["antecedentes_hc_estructurados"] = (
        [dict(item) for item in antecedentes_estructurados if isinstance(item, dict)]
        if isinstance(antecedentes_estructurados, list)
        else []
    )

    procedimientos_hc = normalized.get("procedimientos_hc")
    if isinstance(procedimientos_hc, list):
        normalized["procedimientos_hc"] = _normalize_text_list(procedimientos_hc)
    else:
        normalized["procedimientos_hc"] = []
    if historia_html and not normalized["procedimientos_hc"]:
        normalized["procedimientos_hc"] = _normalize_text_list(
            extraer_procedimientos_historia(historia or historia_html)
        )

    medicamentos_hc = normalized.get("medicamentos_hc")
    if isinstance(medicamentos_hc, list):
        normalized["medicamentos_hc"] = normalizar_lista_medicamentos(
            medicamentos_hc,
            fuente="historia_clinica",
        )
    else:
        normalized["medicamentos_hc"] = []
    if historia and not normalized["medicamentos_hc"]:
        normalized["medicamentos_hc"] = normalizar_lista_medicamentos(
            extraer_medicamentos_historia(historia or historia_html),
            fuente="historia_clinica",
        )

    medicamentos_hc_display = normalized.get("medicamentos_hc_display")
    if isinstance(medicamentos_hc_display, list):
        normalized["medicamentos_hc_display"] = _normalize_text_list(medicamentos_hc_display)
    else:
        normalized["medicamentos_hc_display"] = []
    if normalized["medicamentos_hc"] and not normalized["medicamentos_hc_display"]:
        normalized["medicamentos_hc_display"] = build_medication_display_list(normalized["medicamentos_hc"])


def _normalize_factura_base(normalized: dict[str, Any]) -> None:
    normalized["factura"] = normalize_factura_medicamentos(normalized.get("factura"))
    normalized["procedimientos_factura"] = [
        item
        for item in (normalized.get("procedimientos_factura") or [])
        if isinstance(item, dict)
        and is_valid_factura_procedimiento(
            item.get("codigo_cups") or item.get("codigo_referencia"),
            item.get("descripcion") or item.get("concepto"),
        )
    ]
    if isinstance(normalized.get("factura"), dict) and not normalized["procedimientos_factura"]:
        normalized["procedimientos_factura"] = extraer_procedimientos_factura(normalized["factura"])


def _medication_invoice_state(factura: Any) -> MedicationInvoiceState:
    if not isinstance(factura, dict):
        return MedicationInvoiceState.AUSENTE
    factura_json = factura.get("factura_json")
    if not isinstance(factura_json, dict):
        return MedicationInvoiceState.INSUFICIENTE
    servicios = factura_json.get("servicios_procedimientos")
    if not isinstance(servicios, dict):
        return MedicationInvoiceState.INSUFICIENTE
    return MedicationInvoiceState.VALIDA


def _medication_support_documents(context: dict[str, Any]) -> list[dict[str, str]]:
    support: list[dict[str, str]] = []
    groups = (
        ("historia_clinica", [context.get("historia")]),
        ("quirurgico", [context.get("quirurgico")]),
        ("generico", context.get("generico") or []),
        ("radiologia", context.get("radiologia") or []),
        ("laboratorio", context.get("laboratorio") or []),
    )
    for document_type, documents in groups:
        for document in documents:
            if not isinstance(document, dict):
                continue
            text = _normalize_whitespace(
                document.get("descripcion")
                or document.get("texto_extraido")
                or document.get("texto")
                or strip_html_tags(
                    document.get("analisis_html") or render_document_analysis_html(document) or ""
                )
            )
            if not text:
                continue
            filename = _normalize_whitespace(document.get("nombre_archivo"))
            support.append(
                {
                    "fuente": f"{document_type}: {filename}" if filename else document_type,
                    "texto": text,
                }
            )
    return support


def _previous_clinical_medication_reviews(catalog: Any) -> list[dict[str, Any]]:
    reviews: list[dict[str, Any]] = []
    for item in catalog if isinstance(catalog, list) else []:
        if not isinstance(item, dict) or not isinstance(item.get("pertinencia"), dict):
            continue
        assessment = item["pertinencia"]
        if "llm" not in str(assessment.get("origen") or ""):
            continue
        reviews.append(
            {
                "key": str(item.get("key") or ""),
                "estado": str(assessment.get("estado") or ""),
                "justificacion": str(assessment.get("justificacion_clinica") or ""),
                "evidence_ids": list(assessment.get("evidencias_clinicas_ids") or []),
            }
        )
    return reviews


def normalize_context_payload(context: dict[str, Any]) -> dict[str, Any]:
    normalized = dict(context or {})
    previous_medication_catalog = normalized.get("medicamentos_caso")
    normalized["historia"] = _normalize_optional_doc(normalized.get("historia"))
    normalized["quirurgico"] = _normalize_optional_doc(normalized.get("quirurgico"))
    normalized["factura"] = _normalize_optional_doc(normalized.get("factura"))
    normalized["prescripcion"] = _normalize_dict_list(normalized.get("prescripcion"))
    raw_integrated_summary = normalized.get("resumen_clinico_integrado")
    integrated_summary = dict(raw_integrated_summary) if isinstance(raw_integrated_summary, dict) else {}
    summary_text = str(
        integrated_summary.get("texto") or (normalized.get("metadatos_hc") or {}).get("resumen") or ""
    ).strip()
    paragraphs = _normalize_text_list(integrated_summary.get("parrafos"))
    if not paragraphs and summary_text:
        paragraphs = [summary_text]
    integrated_summary.update(
        {
            "estado": str(integrated_summary.get("estado") or "solo_historia").strip(),
            "texto": summary_text,
            "parrafos": paragraphs,
            "fuentes": _normalize_dict_list(integrated_summary.get("fuentes")),
            "advertencia": str(integrated_summary.get("advertencia") or "").strip() or None,
        }
    )
    normalized["resumen_clinico_integrado"] = integrated_summary
    normalized["metadatos_hc"] = dict(normalized.get("metadatos_hc") or {})
    normalized["metadatos_hc"]["resumen"] = summary_text
    normalized["manual_soat_results"] = _normalize_manual_soat_results(normalized.get("manual_soat_results"))
    normalized["auto_soat_results"] = merge_soat_results([], normalized.get("auto_soat_results"))
    normalized["qx_agent_results"] = merge_soat_results([], normalized.get("qx_agent_results"))
    normalized["codigos_desde_soat"] = merge_soat_results([], normalized.get("codigos_desde_soat"))
    normalized["pdf_draft"] = coerce_pdf_draft(normalized.get("pdf_draft"))
    normalized["pdf_preparation_user_metadata"] = coerce_pdf_preparation_user_metadata(
        normalized.get("pdf_preparation_user_metadata")
    )
    try:
        normalized["pdf_preparation_revision"] = max(0, int(normalized.get("pdf_preparation_revision") or 0))
    except (TypeError, ValueError):
        normalized["pdf_preparation_revision"] = 0
    normalized["recomendaciones_medicas"] = _normalize_recomendaciones_medicas(
        normalized.get("recomendaciones_medicas")
    )
    _normalize_historia_clinica_base(normalized)
    _normalize_factura_base(normalized)
    factura_services = _servicios_factura(normalized.get("factura") or {})
    normalized["medicamentos_prescripcion"] = normalizar_lista_medicamentos(
        normalized.get("medicamentos_prescripcion"),
        fuente="prescripcion",
    )
    medication_catalog = reconcile_case_medications(
        [
            *(normalized.get("medicamentos_hc") or []),
            *(normalized.get("medicamentos_prescripcion") or []),
        ],
        factura_services.get("medicamentos") or [],
    )
    normalized["medicamentos_caso"] = evaluate_medication_pertinence(
        medication_catalog,
        invoice_state=_medication_invoice_state(normalized.get("factura")),
        support_documents=_medication_support_documents(normalized),
    )
    normalized["medicamentos_caso"] = apply_clinical_pertinence_reviews(
        normalized["medicamentos_caso"],
        _previous_clinical_medication_reviews(previous_medication_catalog),
    )
    normalized["medicamentos_glosa"] = build_medication_glosa_findings(normalized["medicamentos_caso"])
    normalized["medicamentos_hc_display"] = build_medication_display_list(normalized["medicamentos_caso"])
    normalized["radiologia"] = _normalize_dict_list(normalized.get("radiologia"))
    normalized["laboratorio"] = _normalize_dict_list(normalized.get("laboratorio"))
    normalized["generico"] = _normalize_dict_list(normalized.get("generico"))
    normalized["historias_adicionales"] = _normalize_dict_list(normalized.get("historias_adicionales"))
    rejected_candidates: list[dict[str, Any]] = []
    source_documents = (
        ("historia_clinica", [normalized.get("historia")]),
        ("quirurgico", [normalized.get("quirurgico")]),
        ("generico", normalized.get("generico") or []),
        ("historia_clinica", normalized.get("historias_adicionales") or []),
    )
    for source, documents in source_documents:
        for document in documents:
            if not isinstance(document, dict):
                continue
            rejected_candidates.extend(classify_diagnostic_aid_candidates(document, source)["rejected"])
    normalized["ayudas_diagnosticas_rechazadas"] = {
        str(item.get("candidate_id")): item for item in rejected_candidates if item.get("candidate_id")
    }
    normalized["ayudas_diagnosticas_rechazadas"] = list(normalized["ayudas_diagnosticas_rechazadas"].values())
    ayudas_existentes = coerce_ayudas_diagnosticas(normalized.get("ayudas_diagnosticas"))
    normalized["ayudas_diagnosticas"] = ayudas_existentes or build_ayudas_diagnosticas(normalized)
    normalized["diagnostic_aid_quality"] = build_diagnostic_aid_quality_report(
        normalized, normalized["ayudas_diagnosticas"]
    )
    excluded_ayudas = normalized["pdf_draft"].get("excluded_ayudas_diagnosticas_keys")
    for ayuda in normalized["ayudas_diagnosticas"]:
        ayuda["exportable"] = evaluar_ayuda_diagnostica_exportable(ayuda, excluded_ayudas)
    apply_factura_cost_ordering(normalized)
    normalized["medicamentos_hc_display"] = build_medication_display_list(normalized["medicamentos_caso"])
    normalized["imagenes_diagnosticas"] = _normalize_text_list(normalized.get("imagenes_diagnosticas")) or [
        format_ayuda_diagnostica_presentacion(item)
        for item in normalized["ayudas_diagnosticas"]
        if item.get("tipo") == "imagen"
    ]
    normalized["diagnosticos_consolidados"] = _coerce_diagnostico_catalog(
        normalized.get("diagnosticos_consolidados")
    )
    normalized["diagnosticos_pendientes_validacion"] = _coerce_diagnostico_catalog(
        normalized.get("diagnosticos_pendientes_validacion")
    )
    normalized["diagnosticos_consolidacion_hallazgos"] = _coerce_diagnostico_hallazgos(
        normalized.get("diagnosticos_consolidacion_hallazgos")
    )
    if not normalized["diagnosticos_consolidados"]:
        normalized.update(build_diagnosticos_consolidados(normalized))
    normalized["pdf_draft"] = reconcile_pdf_draft(normalized.get("pdf_draft"), normalized)
    normalized.update(build_pdf_export_blocks(normalized))
    if curation := curation_from_context(normalized):
        normalized.update(
            curation_context_payload(
                curation,
                pdf_preparation_overrides=normalized.get("pdf_draft"),
                diagnostic_aids=normalized.get("ayudas_diagnosticas"),
                source_context=normalized,
            )
        )
        apply_curation_pop_metadata(normalized, curation)
        apply_factura_cost_ordering(normalized)
    normalized["audit_review_items"] = build_audit_review_items(normalized)
    normalized["publication_summary"] = publication_summary(normalized)
    normalized["pdf_candidate_diagnosticos"] = (
        normalized.get("pdf_candidate_diagnosticos")
        if isinstance(normalized.get("pdf_candidate_diagnosticos"), list)
        else []
    )
    normalized["pdf_preparation_procedimientos"] = build_pdf_preparation_procedimientos(normalized)
    normalized.update(build_pdf_chronological_export_blocks(normalized))
    normalized["publication_projections"] = build_publication_projections(normalized)
    return normalized


def is_compatible_cached_context(context: dict[str, Any] | None) -> bool:
    if not isinstance(context, dict):
        return False
    if context.get("orden_costo_version") != COST_ORDER_VERSION:
        return False
    required_keys = {
        "historia",
        "quirurgico",
        "factura",
        "prescripcion",
        "auto_soat_results",
        "qx_agent_results",
        "manual_soat_results",
        "pdf_draft",
        "procedimientos_hc",
        "medicamentos_hc",
        "medicamentos_hc_display",
        "medicamentos_caso",
        "medicamentos_prescripcion",
        "medicamentos_glosa",
        "procedimientos_factura",
        "imagenes_diagnosticas",
        "diagnosticos_consolidados",
        "diagnosticos_pendientes_validacion",
        "diagnosticos_consolidacion_hallazgos",
        "ayudas_diagnosticas",
    }
    if not required_keys.issubset(set(context.keys())):
        return False
    if any(
        context.get(key) is not None and not isinstance(context.get(key), dict)
        for key in ("historia", "quirurgico", "factura")
    ):
        return False

    medication_catalog = context.get("medicamentos_caso")
    if isinstance(medication_catalog, list) and any(
        not isinstance(item, dict)
        or not isinstance(item.get("pertinencia"), dict)
        or item["pertinencia"].get("version") != "v2"
        for item in medication_catalog
    ):
        return False

    return all(
        isinstance(context.get(key), list)
        for key in (
            "procedimientos_hc",
            "medicamentos_hc",
            "medicamentos_hc_display",
            "medicamentos_caso",
            "medicamentos_prescripcion",
            "medicamentos_glosa",
            "prescripcion",
            "procedimientos_factura",
            "imagenes_diagnosticas",
        )
    )
