from __future__ import annotations

import unittest

from app.case_epicrisis.application.use_cases import build_integrated_summary_context
from app.case_epicrisis.domain.models import (
    EpicrisisSummaryComposition,
    EpicrisisSummarySourceUnit,
)
from app.case_epicrisis.infrastructure.summary_composer import (
    EpicrisisSummaryCompositionError,
    LLMEpicrisisSummaryComposer,
)
from app.llm import LLMOutputKind, LLMResolvedRoute, LLMStructuredResult


class _MemoryCache:
    cache_version = "v1"

    def __init__(self) -> None:
        self.items: dict[tuple[str, str, str], dict] = {}

    def get(self, *, username: str, task: str, fingerprint: str):
        return self.items.get((username, task, fingerprint))

    def upsert(self, *, username: str, task: str, fingerprint: str, **values):
        self.items[(username, task, fingerprint)] = dict(values)


class _CompositionRouter:
    def __init__(self, payload: dict) -> None:
        self.payload = payload
        self.calls = 0

    def _resolve_route(self, task, metadata=None):
        return LLMResolvedRoute(
            task=task,
            output_kind=LLMOutputKind.STRUCTURED_OBJECT,
            provider="gemini",
            model="gemini-reasoning",
        )

    def generate_structured(self, request):
        self.calls += 1
        return LLMStructuredResult(
            content=self.payload,
            provider="gemini",
            model="gemini-reasoning",
            output_kind=request.output_kind,
        )


def _sources() -> list[EpicrisisSummarySourceUnit]:
    return [
        EpicrisisSummarySourceUnit(
            identifier="historia_clinica:hc-1",
            document_type="historia_clinica",
            available_date="2026-01-01",
            summary="El 2026-01-01 se diagnosticó S42.0 y se indicó control en 30 días.",
        ),
        EpicrisisSummarySourceUnit(
            identifier="quirurgico:qx-1",
            document_type="quirurgico",
            available_date="2026-01-03",
            summary="El 2026-01-03 se realizó el procedimiento 235401, sin complicaciones.",
        ),
    ]


class EpicrisisSummaryComposerTest(unittest.TestCase):
    def test_valid_composition_is_cached_and_reused(self) -> None:
        payload = {
            "nc": (
                "El 2026-01-01 se diagnosticó S42.0 y se indicó control en 30 días. "
                "El 2026-01-03 se realizó el procedimiento 235401, sin complicaciones."
            ),
            "fc": ["historia_clinica:hc-1", "quirurgico:qx-1"],
        }
        router = _CompositionRouter(payload)
        cache = _MemoryCache()
        composer = LLMEpicrisisSummaryComposer(router, cache)

        first = composer.compose(username="auditor", sources=_sources())
        second = composer.compose(username="auditor", sources=_sources())

        self.assertEqual(first, second)
        self.assertEqual(router.calls, 1)
        self.assertEqual(len(cache.items), 1)

    def test_incomplete_coverage_is_rejected_and_not_cached(self) -> None:
        router = _CompositionRouter(
            {
                "nc": "El 2026-01-01 se diagnosticó S42.0 y se indicó control en 30 días.",
                "fc": ["historia_clinica:hc-1"],
            }
        )
        cache = _MemoryCache()

        with self.assertRaises(EpicrisisSummaryCompositionError):
            LLMEpicrisisSummaryComposer(router, cache).compose(username="auditor", sources=_sources())

        self.assertEqual(cache.items, {})


class IntegratedSummaryContextTest(unittest.TestCase):
    class _Composer:
        def compose(self, *, username, sources):
            return EpicrisisSummaryComposition(
                narrative="Narrativa integrada sin reducción.",
                covered_source_ids=[source.identifier for source in sources],
            )

    class _FailingComposer:
        def compose(self, *, username, sources):
            raise RuntimeError("provider unavailable")

    @staticmethod
    def _documents():
        return (
            {"_id": "hc-1", "fecha_analisis": "2026-01-01"},
            {
                "_id": "qx-1",
                "analysis_structured": {
                    "dt": "quirurgico",
                    "rs": "Resumen quirúrgico original 235401.",
                    "fp": "2026-01-03",
                },
            },
            {"resumen": "Resumen de historia original S42.0.", "fecha_ingreso": "2026-01-01"},
        )

    def test_success_sets_integrated_state(self) -> None:
        historia, quirurgico, metadata = self._documents()

        result = build_integrated_summary_context(
            username="auditor",
            historia=historia,
            quirurgico=quirurgico,
            metadatos_hc=metadata,
            summary_composer=self._Composer(),
        )

        self.assertEqual(result["estado"], "integrado")
        self.assertEqual(result["parrafos"], ["Narrativa integrada sin reducción."])
        self.assertIsNone(result["advertencia"])

    def test_provider_failure_preserves_both_sources_as_separate_paragraphs(self) -> None:
        historia, quirurgico, metadata = self._documents()

        result = build_integrated_summary_context(
            username="auditor",
            historia=historia,
            quirurgico=quirurgico,
            metadatos_hc=metadata,
            summary_composer=self._FailingComposer(),
        )

        self.assertEqual(result["estado"], "fuentes_separadas")
        self.assertEqual(
            result["parrafos"],
            ["Resumen de historia original S42.0.", "Resumen quirúrgico original 235401."],
        )
        self.assertIn("No fue posible integrar cronológicamente", result["advertencia"])

    def test_history_only_remains_unchanged_without_warning(self) -> None:
        historia, _, metadata = self._documents()

        result = build_integrated_summary_context(
            username="auditor",
            historia=historia,
            quirurgico=None,
            metadatos_hc=metadata,
            summary_composer=self._FailingComposer(),
        )

        self.assertEqual(result["estado"], "solo_historia")
        self.assertEqual(result["texto"], metadata["resumen"])
        self.assertIsNone(result["advertencia"])


class EpicrisisSummaryPresentationContractTest(unittest.TestCase):
    def test_interactive_warning_and_pdf_paragraph_contract_are_present(self) -> None:
        with open("web/includes/epicrisis/resumen_clinico.html", encoding="utf-8") as template_file:
            template = template_file.read()
        with open("static/js/epicrisis/pdf.js", encoding="utf-8") as script_file:
            pdf_script = script_file.read()

        self.assertIn("resumen_clinico_integrado.advertencia", template)
        self.assertIn("Regenerar epicrisis", template)
        self.assertIn("resumenParrafos", pdf_script)
        self.assertNotIn("advertencia", pdf_script)


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