from __future__ import annotations

import shutil
import subprocess
import textwrap
from pathlib import Path

import pytest
from fastapi.templating import Jinja2Templates

from app.services.factura_presentation import build_factura_view


ROOT = Path(__file__).resolve().parents[1]
SUBVIEWS = (
    "auditoria",
    "resumen-clinico",
    "preparar-pdf",
    "codificacion-cirugia",
    "factura",
    "glosas",
    "metadatos",
)


def render_epicrisis(**context: object) -> str:
    template = Jinja2Templates(directory=str(ROOT / "web")).env.get_template("epicrisis.html")
    return template.render(nombre_paciente="Paciente de prueba", **context)


def test_epicrisis_renders_accessible_ordered_subviews_with_audit_selected() -> None:
    rendered = render_epicrisis()

    assert rendered.count('role="tab"') == 11
    assert rendered.count('role="tabpanel"') == 11
    positions = [rendered.index(f'data-subview="{subview}"') for subview in SUBVIEWS]
    assert positions == sorted(positions)
    assert 'role="tab" aria-selected="true" aria-controls="auditoria"' in rendered
    for subview in SUBVIEWS:
        assert f'id="tab-{subview}"' in rendered
        assert f'id="{subview}"' in rendered
        assert f'aria-labelledby="tab-{subview}"' in rendered
    assert rendered.count('data-preparation-tab="') == 4
    assert rendered.count('data-preparation-panel="') == 4


def test_epicrisis_keeps_all_panels_in_dom_and_renders_safe_empty_states() -> None:
    rendered = render_epicrisis()

    assert 'data-subview-panel="auditoria"' in rendered
    assert 'data-subview-panel="factura"' in rendered
    assert 'data-subview-panel="glosas"' in rendered
    assert "Auditoría aún sin datos de conciliación" in rendered
    assert "Factura no disponible" in rendered
    assert "Sin evaluación estructurada de glosas" in rendered
    assert "Este estado no descarta condiciones pendientes de evaluación." in rendered
    assert 'id="seleccion_pdf_card"' in rendered
    assert 'id="form_codigos_soat"' in rendered


def test_audit_workspace_groups_pending_items_without_expanded_forms() -> None:
    rendered = render_epicrisis(
        case_key="case-1",
        curation_schema_version="v1",
        diagnosticos_curados=[
            {
                "item_id": "diagnosis-1",
                "code": "S202",
                "description": "Contusión del tórax",
                "status": "pendiente_revision",
                "evidence": [],
            }
        ],
        procedimientos_curados=[],
        conflictos_curacion=[],
        catalogos_utilizados=[],
    )

    assert 'class="audit-workspace"' in rendered
    assert 'data-audit-tab="todos"' in rendered
    assert 'data-audit-tab="diagnosticos"' in rendered
    assert 'data-audit-tab="procedimientos"' in rendered
    assert 'data-audit-panel="todos"' in rendered
    assert 'data-audit-panel="diagnosticos" hidden' in rendered
    assert 'data-audit-panel="procedimientos" hidden' in rendered
    assert rendered.count('data-audit-open="audit-review-diagnostico-') == 2
    assert '>Revisar</button>' in rendered
    assert 'data-audit-edit-form hidden' in rendered
    assert '>Aprobar</button>' not in rendered
    assert '>Corregir</button>' not in rendered
    assert '>Resolver issue</button>' in rendered


def test_audit_workspace_exposes_risk_order_evidence_and_human_history() -> None:
    rendered = render_epicrisis(
        case_key="case-1",
        curation_schema_version="v1",
        curation_version="version-1",
        diagnosticos_curados=[
            {
                "item_id": "diagnosis-conflict",
                "code": "S42",
                "description": "Diagnóstico en conflicto",
                "status": "conflicto",
                "evidence": [{"document_type": "historia_clinica", "filename": "historia.pdf", "page": 2}],
                "decisions": [],
            }
        ],
        procedimientos_curados=[
            {
                "item_id": "procedure-1",
                "codigo_cups": "793101",
                "description": "Procedimiento conciliado",
                "status": "corregido",
                "states": ["realizado", "facturado"],
                "evidence": [],
                "decisions": [{
                    "decision": "correct",
                    "actor": "auditora",
                    "decided_at": "2026-08-18T15:00:00Z",
                    "original_code": "793100",
                    "original_description": "Propuesta original",
                    "corrected_code": "793101",
                    "corrected_description": "Procedimiento conciliado",
                    "reason": "Validado contra soporte",
                }],
            }
        ],
        conflictos_curacion=[{"message": "Conflicto vinculado", "item_ids": ["diagnosis-conflict"]}],
        catalogos_utilizados=[],
    )

    assert 'data-audit-risk="1"' in rendered
    assert "historia.pdf" in rendered
    assert "página 2" in rendered
    assert "Decisiones registradas" in rendered
    assert "Validado contra soporte" in rendered
    assert 'data-audit-date' in rendered
    assert "Alertas sin hallazgo vinculado" not in rendered


def test_audit_controller_uses_shared_tabs_and_preserves_patch_contract() -> None:
    source = (ROOT / "static" / "js" / "epicrisis" / "curation.js").read_text()

    assert 'storageKey: "epicrisis:auditoria-active-tab"' in source
    assert "global.EpicrisisApp?.tabs?.create" in source
    assert 'requestedAction === "reject" ? "reject" : changed ? "correct" : "approve"' in source
    assert 'method: "PATCH"' in source
    assert "expected_version: version" in source
    assert 'timeZone: "America/Bogota"' in source
    assert 'opener?.focus()' in source


def test_epicrisis_tab_indicators_use_only_reliable_semantic_states() -> None:
    neutral = render_epicrisis()
    assert 'id="tab-auditoria" class="epicrisis-subview-tab epicrisis-subview-tab--neutral"' in neutral
    assert 'id="tab-glosas" class="epicrisis-subview-tab epicrisis-subview-tab--neutral"' in neutral
    assert 'id="tab-metadatos" class="epicrisis-subview-tab epicrisis-subview-tab--neutral"' in neutral

    success = render_epicrisis(
        curation_schema_version="v1",
        diagnosticos_curados=[],
        procedimientos_curados=[],
        conflictos_curacion=[],
        historia={
            "tipo_documento": "historia_clinica",
            "extraction_metadata": {"total_pages": 1, "pages_with_text": 1},
            "historia_processing": {"coverage_ratio": 1.0, "chunks_succeeded": 1, "chunks_total": 1},
            "analysis_provider": "gemini",
            "analysis_model_name": "gemini-2.5-flash",
            "review_required": False,
        },
    )
    assert 'id="tab-auditoria" class="epicrisis-subview-tab epicrisis-subview-tab--success"' in success
    assert 'id="tab-metadatos" class="epicrisis-subview-tab epicrisis-subview-tab--success"' in success

    alert = render_epicrisis(
        curation_schema_version="v1",
        curation_generation_blocked=True,
        diagnosticos_curados=[],
        procedimientos_curados=[],
        conflictos_curacion=[],
        medicamentos_glosa=[
            {
                "estado": "revisar",
                "medicamento": "Medicamento",
                "cadena": {"orden": "ausente", "administracion": "ausente", "facturacion": "ausente"},
                "fuentes_por_eslabon": {"orden": [], "administracion": [], "facturacion": []},
            }
        ],
        historia={
            "tipo_documento": "historia_clinica",
            "analysis_quality": {"status": "degraded"},
            "review_required": True,
        },
    )
    assert 'id="tab-auditoria" class="epicrisis-subview-tab epicrisis-subview-tab--alert"' in alert
    assert 'id="tab-glosas" class="epicrisis-subview-tab epicrisis-subview-tab--alert"' in alert
    assert 'id="tab-metadatos" class="epicrisis-subview-tab epicrisis-subview-tab--alert"' in alert


def test_subview_controller_handles_hash_history_top_anchor_and_keyboard() -> None:
    node = shutil.which("node") or shutil.which("nodejs")
    if not node:
        pytest.skip("node no está disponible en el entorno de pruebas")

    source_path = ROOT / "static" / "js" / "epicrisis" / "subviews.js"
    script = textwrap.dedent(
        f"""
        const fs = require("node:fs");
        const vm = require("node:vm");
        const ids = {list(SUBVIEWS)!r};
        const listeners = {{}};
        const makeClassList = () => ({{ add() {{}}, remove() {{}}, contains() {{ return false; }} }});
        const tabs = ids.map((id) => ({{
          dataset: {{ subview: id }},
          tabIndex: -1,
          attrs: {{}},
          events: {{}},
          setAttribute(name, value) {{ this.attrs[name] = value; }},
          addEventListener(name, handler) {{ this.events[name] = handler; }},
          focus(options) {{ this.focusOptions = options; }},
        }}));
        const panels = ids.map((id) => ({{ dataset: {{ subviewPanel: id }}, hidden: false }}));
        const header = {{ getBoundingClientRect() {{ return {{ height: 73.2 }}; }} }};
        const styleValues = {{}};
        const document = {{
          body: {{ classList: makeClassList() }},
          documentElement: {{ style: {{ setProperty(name, value) {{ styleValues[name] = value; }} }} }},
          querySelector(selector) {{
            if (selector.includes("tablist")) return {{ querySelectorAll() {{ return tabs; }} }};
            if (selector === ".ui-header") return header;
            return null;
          }},
          querySelectorAll() {{ return panels; }},
        }};
        const context = {{
          document,
          location: {{ hash: "#glosas" }},
          history: {{ pushState(_state, _title, hash) {{ context.location.hash = hash; }} }},
          addEventListener(name, handler) {{ listeners[name] = handler; }},
          EpicrisisApp: {{}},
          ResizeObserver: class {{ observe() {{}} disconnect() {{}} }},
        }};
        context.globalThis = context;
        vm.runInNewContext(fs.readFileSync({str(source_path)!r}, "utf8"), context);
        context.EpicrisisApp.subviews.init();

        if (tabs[5].attrs["aria-selected"] !== "true" || !panels[0].hidden || panels[5].hidden) {{
          throw new Error("El hash directo no activó Glosas");
        }}
        if (styleValues["--epicrisis-header-height"] !== "74px") {{
          throw new Error("La barra sticky no usa la altura real del encabezado");
        }}

        let prevented = false;
        tabs[0].events.keydown({{ key: "End", currentTarget: tabs[0], preventDefault() {{ prevented = true; }} }});
        if (!prevented || context.location.hash !== "#metadatos" || tabs[6].focusOptions?.preventScroll !== true) {{
          throw new Error("End no activó la última pestaña sin forzar scroll");
        }}

        context.location.hash = "#epicrisis_top";
        listeners.hashchange();
        if (tabs[6].attrs["aria-selected"] !== "true") {{
          throw new Error("epicrisis_top modificó la subvista activa");
        }}

        context.location.hash = "#desconocida";
        listeners.hashchange();
        if (tabs[0].attrs["aria-selected"] !== "true") {{
          throw new Error("Un hash desconocido no volvió a Auditoría");
        }}

        context.location.hash = "#factura";
        listeners.popstate();
        if (tabs[4].attrs["aria-selected"] !== "true") {{
          throw new Error("La navegación atrás/adelante no restauró Factura");
        }}
        """
    )
    completed = subprocess.run([node, "-e", script], capture_output=True, text=True, check=False)
    assert completed.returncode == 0, completed.stderr or completed.stdout


def test_factura_workspace_renders_ordered_tabs_structured_sections_and_soat_details() -> None:
    factura = {
        "factura_json": {
            "proveedor": {"nombre_institucion": "Clínica Central", "nit": "900123"},
            "informacion_factura": {
                "numero_factura": "FAC-001",
                "fecha_emision": "2026-08-18T15:30:00Z",
            },
            "informacion_paciente": {
                "nombre_completo": "Paciente Factura",
                "fecha_ingreso": "2026-08-17T20:15:00Z",
            },
            "lineas_canonicas": [
                {
                    "fecha_servicio": "2026-08-18",
                    "codigo_referencia": "793101",
                    "descripcion": "Osteosíntesis",
                    "total": "$900.000",
                }
            ],
            "servicios_procedimientos": {
                "procedimientos_quirurgicos": [
                    {
                        "codigo_cups": "793101",
                        "codigo_facturacion": "13170",
                        "descripcion": "Osteosíntesis",
                        "grupo_quirurgico_soat": 7,
                        "valoracion_soat": {
                            "estado": "liquidada",
                            "regla_aplicada": "cirugia_unica_tarifa_base",
                            "valor_base": 1000,
                            "valor_liquidado": 1000,
                            "evidencia": ["Coincidencia exacta por código"],
                            "componentes": {"cirujano": 1000},
                            "codigos_componentes": {"cirujano": "13170-C"},
                            "indice_linea_canonica": 0,
                        },
                    }
                ],
                "medicamentos": [{"medicamento": "Dipirona", "cantidad": "1"}],
            },
            "resumen_tarifario_soat": {
                "total_base": 1000,
                "total_liquidado": 1000,
                "pendientes": 0,
                "version_catalogo": "SOAT 2026",
            },
        }
    }
    rendered = render_epicrisis(factura=factura, factura_view=build_factura_view(factura))

    assert [
        rendered.index('data-factura-tab="canonica"'),
        rendered.index('data-factura-tab="secciones"'),
        rendered.index('data-factura-tab="soat"'),
    ] == sorted(
        [
            rendered.index('data-factura-tab="canonica"'),
            rendered.index('data-factura-tab="secciones"'),
            rendered.index('data-factura-tab="soat"'),
        ]
    )
    assert rendered.count('data-factura-tab="') == 3
    assert 'aria-selected="true" aria-controls="factura-panel-canonica"' in rendered
    assert 'data-factura-panel="canonica"' in rendered and 'data-factura-panel="secciones"' in rendered
    assert 'data-factura-panel="soat"' in rendered
    assert 'data-invoice-section="procedimientos_quirurgicos"' in rendered
    assert rendered.count('data-invoice-section="') == 8
    assert "Osteosíntesis" in rendered
    assert "Dipirona" in rendered
    assert "SOAT 2026" in rendered
    assert "cirugia_unica_tarifa_base" in rendered
    assert "Coincidencia exacta por código" in rendered
    assert rendered.count("data-local-date") == 3
    assert 'datetime="2026-08-18T15:30:00Z"' in rendered

    stylesheet = (ROOT / "static/css/epicrisis.css").read_text(encoding="utf-8")
    assert ".invoice-tab-panel[hidden]" in stylesheet
    assert "display: none" in stylesheet[stylesheet.index(".invoice-tab-panel[hidden]") :]


def test_factura_workspace_handles_empty_valuation_and_legacy_fallback() -> None:
    structured = {"factura_json": {"servicios_procedimientos": {}}}
    view = build_factura_view(structured)
    rendered = render_epicrisis(factura=structured, factura_view=view)
    assert view["is_structured"] is True
    assert "No hay líneas canónicas persistidas." in rendered
    assert "Sin evaluación SOAT persistida" in rendered
    assert rendered.count("No hay registros en esta sección.") == 8

    legacy = {"analisis_html": "<p>Factura legacy sin bloques reconocibles</p>"}
    legacy_view = build_factura_view(legacy)
    legacy_rendered = render_epicrisis(factura=legacy, factura_view=legacy_view)
    assert legacy_view["is_structured"] is False
    assert "Factura legacy sin bloques reconocibles" in legacy_rendered
    assert 'data-factura-tab="canonica"' not in legacy_rendered


def test_factura_controller_keeps_hash_independent_and_restores_session_tab() -> None:
    node = shutil.which("node") or shutil.which("nodejs")
    if not node:
        pytest.skip("node no está disponible en el entorno de pruebas")

    tabs_source_path = ROOT / "static" / "js" / "epicrisis" / "tabs.js"
    factura_source_path = ROOT / "static" / "js" / "epicrisis" / "factura.js"
    script = textwrap.dedent(
        f"""
        const fs = require("node:fs");
        const vm = require("node:vm");
        const names = ["canonica", "secciones", "soat"];
        const stored = {{ "epicrisis:factura-active-tab": "soat" }};
        const workspace = {{ querySelectorAll(selector) {{
          return selector.includes("data-factura-tab") ? tabs : panels;
        }} }};
        const tabs = names.map((name) => ({{
          dataset: {{ facturaTab: name }}, attrs: {{}}, events: {{}}, tabIndex: -1,
          setAttribute(name, value) {{ this.attrs[name] = value; }},
          addEventListener(name, handler) {{ this.events[name] = handler; }},
          focus(options) {{ this.focusOptions = options; }},
        }}));
        const panels = names.map((name) => ({{ dataset: {{ facturaPanel: name }}, hidden: false }}));
        const context = {{
          document: {{ getElementById(id) {{ return id === "factura_workspace" ? workspace : null; }} }},
          sessionStorage: {{ getItem(key) {{ return stored[key] || null; }}, setItem(key, value) {{ stored[key] = value; }} }},
          location: {{ hash: "#factura" }},
          EpicrisisApp: {{}},
        }};
        context.globalThis = context;
        vm.runInNewContext(fs.readFileSync({str(tabs_source_path)!r}, "utf8"), context);
        vm.runInNewContext(fs.readFileSync({str(factura_source_path)!r}, "utf8"), context);
        context.EpicrisisApp.factura.init();
        if (tabs[2].attrs["aria-selected"] !== "true" || panels[0].hidden !== true || panels[2].hidden !== false) throw new Error("No restauró la pestaña SOAT");
        let prevented = false;
        tabs[2].events.keydown({{ key: "ArrowRight", currentTarget: tabs[2], preventDefault() {{ prevented = true; }} }});
        if (!prevented || tabs[0].attrs["aria-selected"] !== "true" || tabs[0].focusOptions.preventScroll !== true) throw new Error("La navegación circular falló");
        if (context.location.hash !== "#factura") throw new Error("El controlador alteró el hash principal");
        if (stored["epicrisis:factura-active-tab"] !== "canonica") throw new Error("No persistió la pestaña activa");
        """
    )
    completed = subprocess.run([node, "-e", script], capture_output=True, text=True, check=False)
    assert completed.returncode == 0, completed.stderr or completed.stdout


def test_shared_tab_controller_isolates_workspaces_and_storage_keys() -> None:
    node = shutil.which("node") or shutil.which("nodejs")
    if not node:
        pytest.skip("node no está disponible en el entorno de pruebas")

    source_path = ROOT / "static" / "js" / "epicrisis" / "tabs.js"
    script = textwrap.dedent(
        f"""
        const fs = require("node:fs");
        const vm = require("node:vm");
        const stored = {{ "workspace:a": "a2", "workspace:b": "b1" }};
        function makeWorkspace(prefix) {{
          const names = [`${{prefix}}1`, `${{prefix}}2`];
          const tabs = names.map((name) => ({{
            dataset: {{ value: name }}, attrs: {{}}, events: {{}}, tabIndex: -1,
            setAttribute(key, value) {{ this.attrs[key] = value; }},
            addEventListener(key, handler) {{ this.events[key] = handler; }},
            focus(options) {{ this.focusOptions = options; }},
          }}));
          const panels = names.map((name) => ({{ dataset: {{ panel: name }}, hidden: false }}));
          return {{ tabs, panels, root: {{ querySelectorAll(selector) {{ return selector === "[data-value]" ? tabs : panels; }} }} }};
        }}
        const a = makeWorkspace("a");
        const b = makeWorkspace("b");
        const context = {{
          EpicrisisApp: {{}},
          sessionStorage: {{ getItem(key) {{ return stored[key] || null; }}, setItem(key, value) {{ stored[key] = value; }} }},
        }};
        context.globalThis = context;
        vm.runInNewContext(fs.readFileSync({str(source_path)!r}, "utf8"), context);
        const create = context.EpicrisisApp.tabs.create;
        create({{ root: a.root, tabSelector: "[data-value]", panelSelector: "[data-panel]", tabDataKey: "value", panelDataKey: "panel", defaultTab: "a1", storageKey: "workspace:a" }});
        create({{ root: b.root, tabSelector: "[data-value]", panelSelector: "[data-panel]", tabDataKey: "value", panelDataKey: "panel", defaultTab: "b1", storageKey: "workspace:b" }});
        a.tabs[1].events.keydown({{ key: "ArrowRight", currentTarget: a.tabs[1], preventDefault() {{}} }});
        if (stored["workspace:a"] !== "a1" || stored["workspace:b"] !== "b1") throw new Error("Los workspaces compartieron estado");
        if (b.tabs[0].attrs["aria-selected"] !== "true" || b.panels[0].hidden) throw new Error("La navegación alteró otro workspace");
        """
    )
    completed = subprocess.run([node, "-e", script], capture_output=True, text=True, check=False)
    assert completed.returncode == 0, completed.stderr or completed.stdout
