from __future__ import annotations

import unittest
from pathlib import Path

from pydantic import ValidationError

from app.case_epicrisis.application.utils import (
    build_ayudas_diagnosticas,
    build_pdf_primary_ayudas_diagnosticas,
    build_pdf_primary_medicamentos,
    build_pdf_primary_procedimientos,
    build_pdf_selectable_procedimientos,
    build_procedimientos_clinicos,
    coerce_pdf_draft,
    extract_incapacidad_clinica,
    extract_recomendaciones_medicas,
    format_ayuda_diagnostica_interpretacion_exportable,
    format_ayuda_diagnostica_nombre_exportable,
    normalize_context_payload,
)
from app.case_epicrisis.domain.models import RecomendacionMedica
from app.services.clinical_processing import normalizar_medicamento_canonico


class ClinicalProcedureConsolidationTest(unittest.TestCase):
    def test_invoice_procedures_keep_duplicates_order_by_group_and_hide_financial_metadata(self) -> None:
        invoice_items = [
            {
                "codigo_cups": "793101",
                "descripcion": "Osteosíntesis",
                "grupo_quirurgico_soat": 7,
                "valoracion_soat": {
                    "estado": "liquidada",
                    "regla_aplicada": "multiple",
                    "fuente_asignacion": "codigo_soat_explicito",
                    "indice_linea_canonica": 0,
                },
                "costo_quirurgico_asociado": {"valor_final": "344200"},
            },
            {
                "codigo_cups": "765401",
                "descripcion": "Extracción de cuerpos libres",
                "grupo_quirurgico_soat": 7,
                "valoracion_soat": {
                    "estado": "liquidada",
                    "regla_aplicada": "multiple",
                    "fuente_asignacion": "codigo_soat_explicito",
                    "indice_linea_canonica": 1,
                },
                "costo_quirurgico_asociado": {"valor_final": "344200"},
            },
            {
                "codigo_cups": "818307",
                "descripcion": "Condroplastia",
                "grupo_quirurgico_soat": 11,
                "valoracion_soat": {
                    "estado": "liquidada",
                    "regla_aplicada": "multiple",
                    "fuente_asignacion": "codigo_soat_explicito",
                    "indice_linea_canonica": 2,
                },
                "costo_quirurgico_asociado": {"valor_final": "3228300"},
            },
            {
                "codigo_cups": "817204",
                "descripcion": "Ligamentorrafia",
                "grupo_quirurgico_soat": 10,
                "valoracion_soat": {
                    "estado": "liquidada",
                    "regla_aplicada": "multiple",
                    "fuente_asignacion": "codigo_soat_explicito",
                    "indice_linea_canonica": 3,
                },
                "costo_quirurgico_asociado": {"valor_final": "598800"},
            },
            {
                "codigo_cups": "793101",
                "descripcion": "Osteosíntesis",
                "grupo_quirurgico_soat": 7,
                "valoracion_soat": {
                    "estado": "liquidada",
                    "regla_aplicada": "multiple",
                    "fuente_asignacion": "codigo_soat_explicito",
                    "indice_linea_canonica": 4,
                },
                "costo_quirurgico_asociado": {"valor_final": "344200"},
            },
        ]
        context = {
            "procedimientos_factura": [
                {"codigo_cups": item["codigo_cups"], "descripcion": item["descripcion"]}
                for item in invoice_items
            ],
            "factura": {
                "factura_json": {
                    "lineas_canonicas": [
                        {
                            "categoria": "procedimientos_quirurgicos",
                            "codigo_referencia": item["codigo_cups"],
                            "descripcion": item["descripcion"],
                            "total": "0",
                        }
                        for item in invoice_items
                    ],
                    "servicios_procedimientos": {"procedimientos_quirurgicos": invoice_items},
                }
            },
            "pdf_draft": {},
        }

        procedures = build_procedimientos_clinicos(context)

        self.assertEqual(
            [item["codigo_cups"] for item in procedures],
            ["818307", "817204", "793101", "765401"],
        )
        self.assertEqual(len(procedures), 4)
        forbidden = {
            "codigo_soat",
            "grupo_quirurgico_soat",
            "costo_facturado",
            "costo_quirurgico_asociado",
            "valoracion_soat",
        }
        self.assertTrue(all(not (forbidden & set(item)) for item in procedures))

    def test_classifies_cups_and_marks_invoice_only_support(self) -> None:
        procedures = build_procedimientos_clinicos(
            {
                "procedimientos_hc": ["890202 Consulta de primera vez"],
                "factura": {
                    "factura_json": {
                        "servicios_procedimientos": {
                            "procedimientos_quirurgicos": [
                                {"codigo_cups": "793101", "descripcion": "Biopsia"}
                            ],
                            "procedimientos_no_quirurgicos": [
                                {"codigo_cups": "890202", "descripcion": "Consulta de primera vez"}
                            ],
                        }
                    }
                },
                "codigos_desde_soat": [],
                "pdf_draft": {},
            }
        )

        by_description = {item["descripcion"]: item for item in procedures}
        self.assertEqual(by_description["Consulta de primera vez"]["clasificacion"], "no_quirurgico")
        self.assertTrue(by_description["Consulta de primera vez"]["soporte_clinico"])
        self.assertEqual(by_description["Biopsia"]["clasificacion"], "quirurgico")
        self.assertFalse(by_description["Biopsia"]["soporte_clinico"])

    def test_manual_classification_has_precedence(self) -> None:
        base = build_procedimientos_clinicos({"procedimientos_hc": ["151001 Consulta"], "pdf_draft": {}})[0]
        result = build_procedimientos_clinicos(
            {
                "procedimientos_hc": ["151001 Consulta"],
                "pdf_draft": {"procedure_classification_overrides": {base["key"]: "quirurgico"}},
            }
        )[0]
        self.assertEqual(result["clasificacion"], "quirurgico")
        self.assertTrue(result["clasificacion_manual"])

    def test_legacy_context_gets_canonical_procedures(self) -> None:
        context = normalize_context_payload({"procedimientos_hc": ["Procedimiento sin código"]})
        self.assertEqual(context["procedimientos_clinicos"][0]["clasificacion"], "sin_clasificar")

    def test_regression_9608745_restores_structured_pdf_procedures_in_legacy_order(self) -> None:
        context = {
            "procedimientos_hc": ["890202 Consulta de primera vez", "Procedimiento sin código"],
            "quirurgico": {"codigos_cups": [{"codigo_cups": "793101", "procedimiento": "Biopsia dirigida"}]},
            "factura": {
                "factura_json": {
                    "servicios_procedimientos": {
                        "procedimientos_no_quirurgicos": [
                            {"codigo_cups": "890202", "descripcion": "Consulta de primera vez"}
                        ]
                    }
                }
            },
            "pdf_draft": {},
        }

        legacy = build_pdf_primary_procedimientos(context)
        selectable = build_pdf_selectable_procedimientos(context)

        self.assertEqual(
            [" - ".join(filter(None, (item["codigo_cups"], item["descripcion"]))) for item in selectable],
            legacy,
        )
        self.assertTrue(all(item["key"] for item in selectable))
        self.assertEqual(
            {item["descripcion"]: item["clasificacion"] for item in selectable},
            {
                "Biopsia dirigida": "quirurgico",
                "Consulta de primera vez": "no_quirurgico",
                "Procedimiento sin código": "sin_clasificar",
            },
        )

    def test_manual_procedure_keeps_stable_key_when_edited_and_requires_classification(self) -> None:
        unresolved = build_pdf_selectable_procedimientos(
            {"pdf_draft": {"manual_procedimientos": ["791201 - Reducción cerrada"]}}
        )[0]
        stable_key = unresolved["key"]
        self.assertEqual(unresolved["clasificacion"], "sin_clasificar")

        edited = build_pdf_selectable_procedimientos(
            {
                "pdf_draft": {
                    "manual_procedimientos": ["791201 - Reducción cerrada editada"],
                    "manual_procedure_entries": [
                        {"key": stable_key, "value": "791201 - Reducción cerrada editada"}
                    ],
                    "procedure_classification_overrides": {stable_key: "quirurgico"},
                }
            }
        )[0]

        self.assertEqual(edited["key"], stable_key)
        self.assertEqual(edited["descripcion"], "Reducción cerrada editada")
        self.assertEqual(edited["clasificacion"], "quirurgico")
        self.assertTrue(edited["clasificacion_manual"])

    def test_unknown_and_unresolved_overrides_are_silently_discarded(self) -> None:
        draft = coerce_pdf_draft(
            {
                "procedure_classification_overrides": {
                    "valid": "no_quirurgico",
                    "unresolved": "sin_clasificar",
                    "unknown": "administrativo",
                }
            }
        )

        self.assertEqual(draft["procedure_classification_overrides"], {"valid": "no_quirurgico"})
        self.assertEqual(
            coerce_pdf_draft({"procedure_classification_overrides": ["quirurgico"]})[
                "procedure_classification_overrides"
            ],
            {},
        )

    def test_manual_procedure_inclusion_state_survives_draft_reconciliation(self) -> None:
        context = normalize_context_payload(
            {
                "pdf_draft": {
                    "manual_procedimientos": ["Manual pendiente"],
                    "excluded_procedimientos": ["Manual pendiente"],
                }
            }
        )

        self.assertEqual(context["pdf_draft"]["excluded_procedimientos"], ["Manual pendiente"])


class ClinicalExtractionRulesTest(unittest.TestCase):
    def test_immediate_medication_defaults_missing_quantity_to_one(self) -> None:
        medication = normalizar_medicamento_canonico(
            {"medicamento": "Dipirona", "dosis": "1 g IV aplicar ahora"}
        )
        self.assertEqual(medication["cantidad"], "1")

    def test_explicit_quantity_is_preserved(self) -> None:
        medication = normalizar_medicamento_canonico(
            {"medicamento": "Dipirona", "dosis": "aplicar ahora", "cantidad": "2"}
        )
        self.assertEqual(medication["cantidad"], "2")

    def test_extracts_only_ordered_incapacity(self) -> None:
        ordered = extract_incapacidad_clinica(
            {"historia": {"texto_extraido": "Se ordena incapacidad por 3 días."}}
        )
        historical = extract_incapacidad_clinica(
            {"historia": {"texto_extraido": "Antecedente de incapacidad hace un año."}}
        )
        self.assertTrue(ordered["ordenada"])
        self.assertEqual(ordered["duracion"], "3")
        self.assertFalse(historical["ordenada"])


class MedicalRecommendationProjectionTest(unittest.TestCase):
    def test_domain_model_accepts_duration_alias_and_rejects_unknown_category(self) -> None:
        recommendation = RecomendacionMedica.model_validate(
            {
                "categoria": "restriccion",
                "indicacion": "<b>Evitar cargar peso</b>",
                "periodo_duracion": "6 semanas",
                "pagina": 7,
                "evidencia": "<em>Evitar cargar peso por 6 semanas</em>",
            }
        )

        self.assertEqual(recommendation.indicacion, "Evitar cargar peso")
        self.assertEqual(recommendation.duracion, "6 semanas")
        self.assertEqual(recommendation.pagina, 7)
        with self.assertRaises(ValidationError):
            RecomendacionMedica(categoria="dieta", indicacion="Dieta blanda")

    def test_projects_persisted_recommendations_and_formulated_medications_in_stable_order(self) -> None:
        historia = {
            "tipo_documento": "historia_clinica",
            "analysis_structured": {
                "dt": "historia_clinica",
                "pn": "Paciente Demo",
                "rs": "Resumen",
                "rm": [
                    {
                        "ct": "incapacidad",
                        "i": "Incapacidad laboral",
                        "pd": "30 días",
                        "f": "2026-07-30",
                        "pg": 8,
                        "ev": "Se expide incapacidad por 30 días",
                    },
                    {"ct": "restriccion", "i": "Evitar cargar peso"},
                    {"ct": "restriccion", "i": "Evitar cargar peso"},
                ],
                "md": [
                    {"n": "Dipirona", "ds": "1 g", "tu": "administrado"},
                    {
                        "n": "Acetaminofén",
                        "ds": "500 mg",
                        "f": "cada 8 horas",
                        "du": "5 días",
                        "tu": "formulado",
                    },
                ],
            },
        }

        projected = extract_recomendaciones_medicas(historia)

        self.assertEqual(
            [item["categoria"] for item in projected],
            ["incapacidad", "restriccion", "tratamiento_cuidado"],
        )
        self.assertEqual(projected[0]["pagina"], 8)
        self.assertEqual(projected[0]["evidencia"], "Se expide incapacidad por 30 días")
        self.assertIn("Acetaminofén", projected[2]["indicacion"])
        self.assertEqual(projected[2]["duracion"], "5 días")
        self.assertFalse(any("Dipirona" in item["indicacion"] for item in projected))

    def test_missing_context_normalizes_to_empty_without_backfilling_from_history(self) -> None:
        context = normalize_context_payload(
            {
                "historia": {
                    "tipo_documento": "historia_clinica",
                    "analysis_structured": {
                        "dt": "historia_clinica",
                        "pn": "Paciente Demo",
                        "rs": "Resumen",
                        "rm": [{"ct": "otra", "i": "Control ambulatorio"}],
                    },
                }
            }
        )

        self.assertEqual(context["recomendaciones_medicas"], [])

    def test_primary_medications_include_only_administered_and_allow_dual_status(self) -> None:
        medications = build_pdf_primary_medicamentos(
            {
                "medicamentos_caso": [
                    {"nombre": "Dipirona", "medicamento": "Dipirona", "estados": ["administrado"]},
                    {"nombre": "Naproxeno", "medicamento": "Naproxeno", "estados": ["formulado"]},
                    {
                        "nombre": "Acetaminofén",
                        "medicamento": "Acetaminofén",
                        "estados": ["administrado", "formulado"],
                    },
                ]
            }
        )

        self.assertEqual([item["nombre"] for item in medications], ["Dipirona", "Acetaminofén"])


class DiagnosticAidExportTest(unittest.TestCase):
    def test_invoice_aid_uses_code_and_description_as_label_not_interpretation(self) -> None:
        context = {
            "factura": {
                "_id": "fac-1",
                "factura_json": {
                    "servicios_procedimientos": {
                        "imagenologia": [
                            {
                                "estudio": "21602",
                                "descripcion": "Portátiles con fluoroscopia y/o intensificador de imagen",
                            }
                        ],
                        "examenes_laboratorio": [],
                    }
                },
            }
        }

        ayuda = build_ayudas_diagnosticas(context)[0]

        self.assertEqual(
            ayuda["nombre"],
            "21602 - Portátiles con fluoroscopia y/o intensificador de imagen",
        )
        self.assertEqual(ayuda["concepto"], "No interpretado")
        self.assertFalse(ayuda["interpretado"])

        pdf_ayuda = build_pdf_primary_ayudas_diagnosticas({"ayudas_diagnosticas": [ayuda]})[0]
        self.assertEqual(
            format_ayuda_diagnostica_nombre_exportable(pdf_ayuda),
            "21602 - Portátiles con fluoroscopia y/o intensificador de imagen",
        )
        self.assertEqual(format_ayuda_diagnostica_interpretacion_exportable(pdf_ayuda), "")

    def test_legacy_invoice_aid_moves_description_from_concept_to_export_label(self) -> None:
        legacy = {
            "tipo": "imagen",
            "nombre": "21602",
            "concepto": "Portátiles con fluoroscopia y/o intensificador de imagen",
            "fuente": "factura",
            "interpretado": False,
            "facturado": True,
            "exportable": True,
        }

        self.assertEqual(
            format_ayuda_diagnostica_nombre_exportable(legacy),
            "21602 - Portátiles con fluoroscopia y/o intensificador de imagen",
        )
        self.assertEqual(format_ayuda_diagnostica_interpretacion_exportable(legacy), "")


class PdfClinicalExportContractTest(unittest.TestCase):
    def test_procedure_table_exports_cups_without_soat_column(self) -> None:
        pdf_js = Path("static/js/epicrisis/pdf.js").read_text(encoding="utf-8")

        self.assertIn('renderPdfTableContent(["Código CUPS", "Procedimiento"], rows)', pdf_js)
        self.assertNotIn(
            'renderPdfTableContent(["Código CUPS", "Código SOAT", "Procedimiento"], rows)', pdf_js
        )
        self.assertNotIn("codigoSoat", pdf_js)

    def test_selector_and_pdf_expose_only_final_procedure_classifications(self) -> None:
        preparation_js = Path("static/js/epicrisis/preparation.js").read_text(encoding="utf-8")
        pdf_js = Path("static/js/epicrisis/pdf.js").read_text(encoding="utf-8")
        template = Path("web/includes/epicrisis/preparar_pdf.html").read_text(encoding="utf-8")
        stylesheet = Path("static/css/epicrisis.css").read_text(encoding="utf-8")

        self.assertIn('value="quirurgico"', preparation_js)
        self.assertIn('value="no_quirurgico"', preparation_js)
        self.assertNotIn('<option value="sin_clasificar"', preparation_js)
        self.assertNotIn('["sin_clasificar", "Sin clasificar"]', pdf_js)
        self.assertIn("procedimientos_clasificados: procedimientosPdf", pdf_js)
        self.assertIn('class="pdf-list" role="table" aria-label="Procedimientos publicables"', template)
        self.assertNotIn('id="procedimientos_extra"', template)
        self.assertIn(".pdf-list__row--procedimientos", stylesheet)
        self.assertIn("overflow-x: auto;", stylesheet)
        self.assertNotIn("min-width: 9.5rem", stylesheet)


if __name__ == "__main__":
    unittest.main()
