from __future__ import annotations

from pathlib import Path
from types import SimpleNamespace

from fastapi.templating import Jinja2Templates

from app.llm.schemas import HistoriaClinicaStructured
from app.services.clinical_processing import (
    extraer_antecedentes_historia,
    extraer_antecedentes_historia_estructurados,
)
from app.services.historia_antecedentes import (
    antecedentes_to_flat_list,
    extract_historia_antecedentes_structured,
    group_antecedentes_for_view,
    resolve_historia_antecedentes,
)


_ROOT = Path(__file__).resolve().parents[1]


HISTORIA_COLUMNAS = """
ANTECEDENTES
DIABETES : NIEGA,                                      OBESIDAD : NIEGA,
HIPERTENSION ARTERIA : NIEGA,                          CARDIOPATÍA : NIEGA,
TABAQUISMO : NIEGA,                                    ASMA : NIEGA,
ALERGICOS : ******** ALERGICO A                        QUIRURGICOS: APENDICECTOMIA, SAFENECTOMIA
LA NITROFURAZONA ********                              DERECHA.
                                                       FARMACOLOGICOS: NIEGA
                                                       TOXICOS: NIEGA
REVISIÓN POR SISTEMAS

ANTECEDENTES PERONSLES
PATOLOGICOS CARDIOPATIA, HPB, INSUFICIENCIA VENOSA
EXAMEN FISICO

ANTECEDENTES
- PATOLOGICOS: NIEGA
- QUIRURGICOS: APENDICECTOMIA, SAFENECTOMIA DERECHA, CIRUGIA OCULAR EN OJO IZQUIERDO
- ALERGICOS: NIEGA
- TOXICOS: NIEGA
EXAMEN FISICO
"""

HISTORIA_COLUMNAS_COMPACTAS = """
ANTECEDENTES
EPOC : NIEGA,                        OTROS : PATOLOGICOS: NIEGA
ALERGICOS : ************************* ALERGICO A QUIRURGICOS: APENDICECTOMIA, SAFENECTOMIA
LA NITROFURAZONA ***********         DERECHA.
FARMACOLOGICOS: NIEGA
TOXICOS: NIEGA
PRM :
REVISIÓN POR SISTEMAS
\f
ANTECEDENTES
- PATOLOGICOS: NIEGA
- FARMACOLOGICOS: NIEGA
- QUIRURGICOS: APENDICECTOMIA, SAFENECTOMIA DERECHA, CIRUGIA OCULAR EN OJO IZQUIERDO
- ALERGICOS: NIEGA
EXAMEN FISICO
"""


HISTORIA_TABLA = """
ALERGIA: NIEGA
ALERGIAS
AMOXICILINA: PRURITO, RASH
CEFALEXINA: PRURITO, RASH

ANTECEDENTES
FECHA             TIPO                                DESCRIPCIÓN ANTECEDENTES
09/10/2025        QUIRURGICOS                         REEMPLAZO DE RODILLA IZQUIERDA.
09/10/2025        FARMACOLOGICOS                      TAMSLOSINA TAB 0.4 MG 1 TAB VO DIA.
09/10/2025        PATOLOGICOS                         CARDIOPATIA, HPB, INSUFICIENCIA VENOSA.
09/10/2025        ALERGICOS                           CEFALEXINA
17/01/2025        SISTEMICOS                          SOBREPESO
                                                       EXFUMADOR
25/10/2024        ALERGICOS                           AMOXICILINA
30/05/2023        QUIRURGICOS                         REEMPLAZO TOTAL DE RODILLA IZQUIERDA
24/04/2023        PATOLOGICOS                         NIEGA
24/04/2023        ALERGICOS                           NO CONOCIDOS
EXAMEN FISICO
\f
ANTECEDENTES
PATOLÓGICOS: HIPERTENSION ARTERIAL, DISLIPIDEMIA, HPB, OSTEOARTROSIS
QUIRÚRGICO: REEMPLAZO TOTAL DE RODILLA IZQUIERDA.
FARMACOLÓGICOS: TAMSULOSINA, ENALAPRIL, ATORVASTATINA
ALÉRGICOS: AMOXICILINA - CEFALEXINA
EXAMEN FÍSICO
"""


def _values(items, *, category: str, state: str = "presente") -> list[str]:
    return [
        item.descripcion.casefold() for item in items if item.categoria == category and item.estado == state
    ]


def test_extracts_multicolumn_antecedents_and_groups_negations() -> None:
    items = extract_historia_antecedentes_structured(HISTORIA_COLUMNAS)

    assert _values(items, category="alergico") == ["nitrofurazona"]
    assert set(_values(items, category="quirurgico")) == {
        "apendicectomia",
        "cirugia ocular en ojo izquierdo",
        "safenectomia derecha",
    }
    assert "diabetes" in _values(items, category="patologico", state="negado")
    assert "cardiopatia" in _values(items, category="patologico")
    assert "farmacológicos" in _values(items, category="farmacologico", state="negado")

    allergy = next(item for item in items if item.categoria == "alergico" and item.estado == "presente")
    assert allergy.discordante is True
    view = group_antecedentes_for_view(items)
    assert view["categories"][0]["key"] == "alergico"
    assert view["negative_category_count"] >= 3
    assert view["has_discordance"] is True


def test_extracts_compact_multicolumn_antecedents_without_cross_column_artifacts() -> None:
    items = extract_historia_antecedentes_structured(HISTORIA_COLUMNAS_COMPACTAS)
    positives = [item for item in items if item.estado == "presente"]

    assert _values(items, category="alergico") == ["nitrofurazona"]
    assert set(_values(items, category="quirurgico")) == {
        "apendicectomia",
        "cirugia ocular en ojo izquierdo",
        "safenectomia derecha",
    }
    assert all(item.descripcion.casefold() != "alergico a" for item in positives)
    assert all("safenectomia la nitrofurazona" not in item.descripcion.casefold() for item in positives)
    allergy = next(item for item in positives if item.categoria == "alergico")
    assert allergy.discordante is True


def test_resolver_is_idempotent_and_records_discarded_column_artifacts() -> None:
    structured_candidates = [
        {
            "ct": "alergico",
            "d": "Alergia a la nitrofurazona",
            "es": "presente",
            "ev": "ALERGICO A LA NITROFURAZONA",
            "og": "explicito",
            "dc": True,
        },
        {
            "ct": "quirurgico",
            "d": "Apendicectomía",
            "es": "presente",
            "ev": "APENDICECTOMIA",
            "og": "explicito",
        },
        {
            "ct": "quirurgico",
            "d": "Safenectomía derecha",
            "es": "presente",
            "ev": "SAFENECTOMIA DERECHA",
            "og": "explicito",
        },
        {
            "ct": "quirurgico",
            "d": "Cirugía ocular en ojo izquierdo",
            "es": "presente",
            "ev": "CIRUGIA OCULAR EN OJO IZQUIERDO",
            "og": "explicito",
        },
        {
            "ct": "alergico",
            "d": "ALERGICO A",
            "es": "presente",
            "ev": "ALERGICO A",
            "og": "explicito",
        },
        {
            "ct": "quirurgico",
            "d": "SAFENECTOMIA LA NITROFURAZONA DERECHA",
            "es": "presente",
            "ev": "SAFENECTOMIA LA NITROFURAZONA DERECHA",
            "og": "explicito",
        },
    ]
    procedures = [
        {"d": "Apendicectomía"},
        {"d": "Safenectomía derecha"},
        {"d": "Cirugía ocular en ojo izquierdo"},
    ]

    first = resolve_historia_antecedentes(
        HISTORIA_COLUMNAS_COMPACTAS,
        structured_candidates=structured_candidates,
        procedure_candidates=procedures,
    )
    second = resolve_historia_antecedentes(
        HISTORIA_COLUMNAS_COMPACTAS,
        structured_candidates=first.items,
        procedure_candidates=procedures,
        legacy_flat_candidates=antecedentes_to_flat_list(first.items),
    )

    assert first.items == second.items
    assert antecedentes_to_flat_list(first.items) == antecedentes_to_flat_list(second.items)
    assert first.reason_codes == second.reason_codes == ()
    assert first.policy_version == second.policy_version == "v3"
    assert dict(first.rejected_by_reason) == {
        "antecedents_cross_column_contamination": 1,
        "antecedents_incomplete_value": 1,
    }
    assert next(item for item in first.items if item.categoria == "alergico").discordante is True


def test_summary_text_does_not_change_canonical_antecedents() -> None:
    base = {
        "dt": "historia_clinica",
        "ae": [
            {
                "ct": "alergico",
                "d": "Nitrofurazona",
                "es": "presente",
                "ev": "LA NITROFURAZONA",
                "og": "explicito",
            }
        ],
    }
    first = extraer_antecedentes_historia_estructurados(
        {
            "descripcion": HISTORIA_COLUMNAS_COMPACTAS,
            "analysis_structured": {**base, "rs": "Resumen sin antecedentes."},
        }
    )
    second = extraer_antecedentes_historia_estructurados(
        {
            "descripcion": HISTORIA_COLUMNAS_COMPACTAS,
            "analysis_structured": {
                **base,
                "rs": "El resumen menciona información clínica completamente diferente.",
            },
        }
    )

    assert first == second


def test_extracts_dated_table_across_pages_and_consolidates_duplicates() -> None:
    items = extract_historia_antecedentes_structured(HISTORIA_TABLA)

    allergies = _values(items, category="alergico")
    pathologies = _values(items, category="patologico")
    surgical = _values(items, category="quirurgico")
    systemic = _values(items, category="sistemico")

    assert allergies.count("amoxicilina") == 1
    assert any(value.startswith("cefalexina") for value in allergies)
    assert {"cardiopatia", "hipertension arterial", "dislipidemia", "osteoartrosis"} <= set(pathologies)
    assert len([value for value in surgical if "reemplazo" in value]) == 1
    assert "exfumador" in systemic
    assert any(item.pagina == 2 for item in items if item.descripcion.casefold() == "dislipidemia")
    assert all(item.discordante for item in items if item.categoria == "alergico")


def test_structured_projection_preserves_flat_pdf_contract() -> None:
    model = HistoriaClinicaStructured.model_validate(
        {
            "patient_name": "PACIENTE PRUEBA",
            "resumen_clinico": "Resumen clínico válido para el caso de prueba.",
            "antecedentes_estructurados": [
                {
                    "categoria": "alergico",
                    "descripcion": "Penicilina",
                    "estado": "presente",
                    "evidencia": "ALERGICOS: PENICILINA",
                },
                {
                    "categoria": "patologico",
                    "descripcion": "Diabetes",
                    "estado": "negado",
                },
            ],
        }
    )
    document = {
        "tipo_documento": "historia_clinica",
        "descripcion": "",
        "analysis_structured": model.model_dump(by_alias=True, exclude_none=True),
    }

    structured = extraer_antecedentes_historia_estructurados(document)
    flat = extraer_antecedentes_historia(document)

    assert structured[0]["categoria"] == "alergico"
    assert flat == ["ALERGIA: Penicilina"]


def test_flat_projection_labels_allergies_and_pharmacological_context_only() -> None:
    flat = antecedentes_to_flat_list(
        [
            {"ct": "alergico", "d": "NITROFURAZONA"},
            {
                "ct": "farmacologico",
                "d": "Tamsulosina",
                "cx": "indicada para hiperplasia prostática",
            },
            {"ct": "farmacologico", "d": "Enalapril"},
            {"ct": "quirurgico", "d": "Apendicectomía"},
            {"ct": "quirurgico", "d": "Safenectomía derecha"},
            {"ct": "quirurgico", "d": "Cirugía ocular en ojo izquierdo"},
        ]
    )

    assert flat == [
        "ALERGIA: NITROFURAZONA",
        "Apendicectomía",
        "Cirugía ocular en ojo izquierdo",
        "Safenectomía derecha",
        "FARMACOLÓGICO: Enalapril",
        "FARMACOLÓGICO: Tamsulosina — indicada para hiperplasia prostática",
    ]


def test_resolver_accepts_explicit_pharmacological_context_and_records_metrics() -> None:
    raw_text = (
        "Se documenta antecedente farmacológico de Tamsulosina, "
        "indicada para hiperplasia prostática desde 2020."
    )

    resolution = resolve_historia_antecedentes(
        raw_text,
        structured_candidates=[
            {
                "ct": "farmacologico",
                "d": "Tamsulosina",
                "cx": "indicada para hiperplasia prostática desde 2020",
                "ev": raw_text,
                "og": "explicito",
                "cf": "alta",
            }
        ],
    )

    assert resolution.items[0].descripcion == "Tamsulosina"
    assert resolution.items[0].contexto == "indicada para hiperplasia prostática desde 2020"
    assert antecedentes_to_flat_list(resolution.items) == [
        "FARMACOLÓGICO: Tamsulosina — indicada para hiperplasia prostática desde 2020"
    ]
    metrics = resolution.to_metrics()
    assert metrics["pharmacological_contexts_accepted"] == 1
    assert metrics["pharmacological_contexts_omitted"] == 0
    assert metrics["pharmacological_contexts_discarded"] == 0


def test_resolver_discards_invented_pharmacological_context_but_keeps_medication() -> None:
    raw_text = "Se registra antecedente farmacológico de Enalapril."

    resolution = resolve_historia_antecedentes(
        raw_text,
        structured_candidates=[
            {
                "ct": "farmacologico",
                "d": "Enalapril",
                "cx": "uso habitual nocturno desde hace diez años",
                "ev": raw_text,
                "og": "explicito",
                "cf": "alta",
            }
        ],
    )

    assert resolution.items[0].descripcion == "Enalapril"
    assert resolution.items[0].contexto is None
    assert antecedentes_to_flat_list(resolution.items) == ["FARMACOLÓGICO: Enalapril"]
    metrics = resolution.to_metrics()
    assert metrics["pharmacological_contexts_accepted"] == 0
    assert metrics["pharmacological_contexts_omitted"] == 1
    assert metrics["pharmacological_contexts_discarded"] == 1


def test_resolver_legacy_comparison_accepts_old_and_contextualized_projections() -> None:
    raw_text = """
ALERGIAS: NITROFURAZONA
Se registra antecedente farmacológico de Enalapril para control de hipertensión.
"""
    candidates = [
        {"ct": "alergico", "d": "NITROFURAZONA", "ev": "ALERGIAS: NITROFURAZONA"},
        {
            "ct": "farmacologico",
            "d": "Enalapril",
            "cx": "para control de hipertensión",
            "ev": "antecedente farmacológico de Enalapril para control de hipertensión",
            "og": "explicito",
            "cf": "alta",
        },
    ]

    old_projection = resolve_historia_antecedentes(
        raw_text,
        structured_candidates=candidates,
        legacy_flat_candidates=["NITROFURAZONA", "Enalapril"],
    )
    new_projection = resolve_historia_antecedentes(
        raw_text,
        structured_candidates=old_projection.items,
        legacy_flat_candidates=[
            "ALERGIA: NITROFURAZONA",
            "FARMACOLÓGICO: Enalapril — para control de hipertensión",
        ],
    )

    assert old_projection.items == new_projection.items
    assert old_projection.reason_codes == new_projection.reason_codes == ()


def test_resolver_rejects_legacy_narratives_and_derives_flat_projection() -> None:
    raw_text = """
ANTECEDENTES
PATOLÓGICOS: HIPERTENSIÓN ARTERIAL
EXAMEN FÍSICO
Paciente consulta por dolor torácico. Se realiza electrocardiograma y se inicia manejo analgésico.
"""
    narrative = (
        "Motivo de consulta: dolor torácico. Examen físico: paciente estable. "
        "Diagnóstico actual: trauma de tórax. Tratamiento: analgesia y observación."
    )

    resolution = resolve_historia_antecedentes(
        raw_text,
        legacy_flat_candidates=[narrative],
    )

    assert antecedentes_to_flat_list(resolution.items) == ["HIPERTENSIÓN ARTERIAL"]
    assert "antecedents_contaminated_narrative" in resolution.reason_codes
    assert "antecedents_inconsistent_flat_projection" in resolution.reason_codes
    assert all(item.evidencia and item.pagina == 1 for item in resolution.items)


def test_resolver_accepts_traced_high_confidence_inference() -> None:
    raw_text = "En la valoración documental se registra hipertensión arterial."

    resolution = resolve_historia_antecedentes(
        raw_text,
        structured_candidates=[
            {
                "categoria": "patologico",
                "descripcion": "Hipertensión arterial",
                "estado": "presente",
                "origen": "inferido",
                "confianza": "alta",
                "evidencia": "se registra hipertensión arterial",
            }
        ],
    )

    assert antecedentes_to_flat_list(resolution.items) == ["Hipertensión arterial"]
    assert resolution.items[0].origen == "inferido"
    assert resolution.items[0].confianza == "alta"
    assert resolution.reason_codes == ()


def test_resolver_discards_negations_and_current_episode_content() -> None:
    raw_text = """
ANTECEDENTES
PATOLÓGICOS: NIEGA
DIAGNÓSTICO ACTUAL: FRACTURA DE CLAVÍCULA
"""

    resolution = resolve_historia_antecedentes(
        raw_text,
        structured_candidates=[
            {
                "categoria": "patologico",
                "descripcion": "Fractura de clavícula",
                "estado": "presente",
                "origen": "inferido",
                "confianza": "alta",
            }
        ],
    )

    assert resolution.items == ()
    assert resolution.negatives_discarded >= 1
    assert "antecedents_current_episode_content" in resolution.reason_codes


def test_epicrisis_panel_uses_compact_cards_and_keeps_pdf_flat_payload() -> None:
    template = (_ROOT / "web" / "epicrisis.html").read_text(encoding="utf-8")
    css = (_ROOT / "static" / "css" / "epicrisis.css").read_text(encoding="utf-8")
    panel = template.split("<!-- Antecedentes -->", maxsplit=1)[1].split(
        "{% if incapacidad_clinica", maxsplit=1
    )[0]

    assert '<ol class="epicrisis-list">' not in panel
    assert 'class="antecedents-grid"' in panel
    assert 'class="antecedents-negatives"' in panel
    assert "Información discordante" in panel
    assert "antecedentesHC:" in template
    assert "grid-template-columns: repeat(auto-fit" in css
    assert "@media (max-width: 720px)" in css


def test_epicrisis_template_renders_structured_antecedents_and_empty_state() -> None:
    template = Jinja2Templates(directory=str(_ROOT / "web")).env.get_template("epicrisis.html")
    view = group_antecedentes_for_view(
        [
            {
                "categoria": "alergico",
                "descripcion": "Nitrofurazona",
                "estado": "presente",
                "discordante": True,
                "evidencia": "ALERGICOS: NITROFURAZONA",
            },
            {
                "categoria": "quirurgico",
                "descripcion": "Apendicectomía",
                "estado": "presente",
            },
            {
                "categoria": "quirurgico",
                "descripcion": "Safenectomía derecha",
                "estado": "presente",
            },
            {
                "categoria": "alergico",
                "descripcion": "Alérgicos",
                "estado": "negado",
                "discordante": True,
            },
        ]
    )

    rendered = template.render(
        nombre_paciente="",
        user=SimpleNamespace(username="tester"),
        metadatos_hc={"caso": "1390913"},
        antecedentes_hc_view=view,
    )
    empty_rendered = template.render(
        nombre_paciente="",
        user=SimpleNamespace(username="tester"),
        metadatos_hc={"caso": "1390913"},
        antecedentes_hc_view=group_antecedentes_for_view([]),
    )

    assert "Nitrofurazona" in rendered
    assert "Apendicectomía" in rendered
    assert "Safenectomía derecha" in rendered
    assert 'aria-label="2 antecedentes">2</span>' in rendered
    assert "Información discordante" in rendered
    assert "Antecedentes negados" in rendered
    assert "ALERGICOS: NITROFURAZONA" in rendered
    assert 'class="epicrisis-panel epicrisis-panel--antecedents"' not in empty_rendered
