from __future__ import annotations

from typing import Any

from app.case_epicrisis.application.curation import (
    apply_curation_decision,
    curation_context_payload,
    curation_from_context,
)
from app.case_epicrisis.application.rules import HistoriaClinicaRequiredRule
from app.case_epicrisis.application.use_cases import (
    BuildCaseEpicrisisContextUseCase,
    EvaluateEpicrisisRulesUseCase,
    GetCaseEpicrisisContextUseCase,
    UpdateCaseEpicrisisDraftUseCase,
    UpdatePdfPreparationMetadataUseCase,
)
from app.case_epicrisis.application.utils import normalize_context_payload
from app.case_epicrisis.domain.curation import (
    CurationDecisionType,
    SoatCurationValuation,
    dump_curation_model,
)
from app.case_epicrisis.infrastructure.gateways import (
    ServicesEpicrisisCodingGateway,
)
from app.case_epicrisis.infrastructure.medication_pertinence import LLMMedicationPertinenceReviewer
from app.case_epicrisis.infrastructure.repositories import (
    MongoCaseDocumentsReader,
    MongoCaseEpicrisisCacheRepository,
)
from app.case_epicrisis.infrastructure.summary_composer import LLMEpicrisisSummaryComposer
from app.core.logging import bind_log_context, get_audit_logger
from app.soat_crosswalk.infrastructure.catalogs import JsonCupsSoatCrosswalk
from app.soat_tariffs.application.valuation import tariff_entry_values
from app.soat_tariffs.infrastructure.json_catalog import JsonSoatTariffCatalog


class CaseEpicrisisService:
    """Fachada compatible mientras la lógica migra al módulo hexagonal."""

    def __init__(
        self,
        *,
        mongo_analyses: Any,
        client_groq: Any,
        client_gemini: Any = None,
        soat_retriever: Any = None,
        cie10_retriever: Any = None,
        llm_router: Any = None,
        colombia_tz: Any,
        llm_task_cache_repository: Any = None,
        catalog_registry: Any = None,
    ):
        self.audit_logger = get_audit_logger()
        self.catalog_registry = catalog_registry
        self.documents_reader = MongoCaseDocumentsReader(mongo_analyses)
        self.cache_repository = MongoCaseEpicrisisCacheRepository(
            mongo_analyses,
            colombia_tz,
            catalog_registry=catalog_registry,
        )
        self.coding_gateway = ServicesEpicrisisCodingGateway(
            client_groq=client_groq,
            client_gemini=client_gemini,
            soat_retriever=soat_retriever,
            cie10_retriever=cie10_retriever,
            llm_router=llm_router,
            llm_task_cache_repository=llm_task_cache_repository,
        )
        self.rules_evaluator = EvaluateEpicrisisRulesUseCase(rules=[HistoriaClinicaRequiredRule()])
        self.medication_pertinence_reviewer = (
            LLMMedicationPertinenceReviewer(llm_router) if llm_router is not None else None
        )
        self.summary_composer = (
            LLMEpicrisisSummaryComposer(llm_router, llm_task_cache_repository)
            if llm_router is not None
            else None
        )
        self.build_case_context_use_case = BuildCaseEpicrisisContextUseCase(
            documents_reader=self.documents_reader,
            coding_gateway=self.coding_gateway,
            rules_evaluator=self.rules_evaluator,
            medication_pertinence_reviewer=self.medication_pertinence_reviewer,
            summary_composer=self.summary_composer,
        )
        self.get_case_context_use_case = GetCaseEpicrisisContextUseCase(
            cache_repository=self.cache_repository,
            build_case_context_use_case=self.build_case_context_use_case,
        )
        self.update_case_draft_use_case = UpdateCaseEpicrisisDraftUseCase(
            cache_repository=self.cache_repository,
            get_case_context_use_case=self.get_case_context_use_case,
        )
        self.update_pdf_preparation_metadata_use_case = UpdatePdfPreparationMetadataUseCase(
            cache_repository=self.cache_repository,
            get_case_context_use_case=self.get_case_context_use_case,
        )

    def ensure_indexes(self) -> None:
        self.cache_repository.ensure_indexes()

    def build_case_context(self, username: str, case_key: str) -> dict[str, Any]:
        bind_log_context(username=username, case_key=case_key, document_type="epicrisis")
        existing = self.cache_repository.get(username, case_key)
        preserved_context = existing.get("contexto") if existing else None
        context = self.build_case_context_use_case.execute(
            username,
            case_key,
            preserved_context=preserved_context,
        )
        self.audit_logger.business_event(
            event_type="epicrisis.generated",
            action="build_case_context",
            outcome="success",
            service="case_epicrisis_service",
            resource={"case_key": case_key},
        )
        return context

    def get_cached_case_context(self, username: str, case_key: str) -> dict[str, Any] | None:
        bind_log_context(username=username, case_key=case_key, document_type="epicrisis")
        cache_doc = self.cache_repository.get(username, case_key)
        if not cache_doc or not isinstance(cache_doc.get("contexto"), dict):
            return cache_doc
        normalized_doc = dict(cache_doc)
        normalized_doc["contexto"] = normalize_context_payload(cache_doc["contexto"])
        return normalized_doc

    def cache_case_context(
        self,
        username: str,
        case_key: str,
        *,
        regen: bool = False,
    ) -> dict[str, Any]:
        bind_log_context(username=username, case_key=case_key, document_type="epicrisis")
        context = self.get_case_context_use_case.execute(
            username,
            case_key,
            regen=regen,
        )
        self.audit_logger.business_event(
            event_type="epicrisis.cache_refresh" if regen else "epicrisis.generated",
            action="cache_case_context",
            outcome="success",
            service="case_epicrisis_service",
            resource={"case_key": case_key, "regen": bool(regen)},
        )
        return context

    def update_case_draft(
        self,
        username: str,
        case_key: str,
        *,
        payload: dict[str, Any],
    ) -> dict[str, Any]:
        bind_log_context(username=username, case_key=case_key, document_type="epicrisis")
        result = self.update_case_draft_use_case.execute(
            username,
            case_key,
            payload=payload,
        )
        self.audit_logger.business_event(
            event_type="epicrisis.draft_updated",
            action="update_case_draft",
            outcome="success",
            service="case_epicrisis_service",
            resource={"case_key": case_key},
        )
        return result

    def update_pdf_preparation_metadata(
        self,
        username: str,
        case_key: str,
        *,
        procedure_key: str,
        group: int | None,
    ) -> dict[str, Any]:
        bind_log_context(username=username, case_key=case_key, document_type="epicrisis")
        result = self.update_pdf_preparation_metadata_use_case.execute(
            username,
            case_key,
            procedure_key=procedure_key,
            group=group,
        )
        self.audit_logger.business_event(
            event_type="epicrisis.pdf_preparation_metadata_updated",
            action="update_pdf_preparation_metadata",
            outcome="success",
            service="case_epicrisis_service",
            resource={"case_key": case_key, "procedure_key": procedure_key, "cleared": group is None},
        )
        return result

    def get_case_curation(self, username: str, case_key: str) -> dict[str, Any]:
        bind_log_context(username=username, case_key=case_key, document_type="epicrisis")
        context = self.get_case_context_use_case.execute(username, case_key, regen=False)
        curation = curation_from_context(context)
        if curation is None:
            return {
                "curation_schema_version": "",
                "curation_version": "",
                "diagnosticos_curados": [],
                "procedimientos_curados": [],
                "conflictos_curacion": [],
                "pendientes_curacion": [],
                "catalogos_utilizados": [],
                "curation_generation_blocked": False,
                "curation_blocking_reason": "",
            }
        return curation_context_payload(curation)

    def update_case_curation_item(
        self,
        username: str,
        case_key: str,
        *,
        item_id: str,
        decision: str,
        expected_version: str,
        corrected_code: str = "",
        corrected_description: str = "",
        reason: str = "",
        selected_soat_code: str = "",
        calculate_reference: bool = False,
        manual_soat_code: str = "",
        manual_surgical_group: int | None = None,
        selected_components: list[str] | None = None,
        reference_year: int = 2026,
    ) -> dict[str, Any]:
        bind_log_context(username=username, case_key=case_key, document_type="epicrisis")
        context = self.get_case_context_use_case.execute(username, case_key, regen=False)
        curation = curation_from_context(context)
        if curation is None:
            raise LookupError("El caso no tiene datos objetivos para curar.")
        selected_valuation = None
        crosswalk_version = ""
        tariff_version = ""
        source_reference = ""
        allowed_components = {"cirujano", "anestesia", "ayudantia", "sala", "materiales"}
        components_requested = list(dict.fromkeys(selected_components or []))
        if any(component not in allowed_components for component in components_requested):
            raise ValueError("La referencia contiene componentes quirúrgicos no válidos.")
        if calculate_reference and not components_requested:
            raise ValueError("Selecciona al menos un componente para calcular la referencia SOAT.")
        if calculate_reference and not (selected_soat_code or manual_soat_code or manual_surgical_group):
            raise ValueError("Indica un código SOAT exacto o un grupo quirúrgico oficial.")
        effective_manual_soat = manual_soat_code if calculate_reference else ""
        effective_manual_group = manual_surgical_group if calculate_reference else None
        effective_components = components_requested if calculate_reference else []
        requested_soat_code = selected_soat_code or effective_manual_soat
        if requested_soat_code or effective_manual_group:
            procedure = next(
                (item for item in curation.procedures if item.item_id == item_id),
                None,
            )
            if procedure is None:
                raise LookupError("Procedimiento de curación no encontrado.")
            tariff_catalog = JsonSoatTariffCatalog()
            catalog = tariff_catalog.load(reference_year)
            selected_candidate = next(
                (item for item in procedure.soat_candidates if item.soat_code == requested_soat_code),
                None,
            )
            entry = tariff_catalog.find_exact(reference_year, requested_soat_code) if requested_soat_code else None
            if requested_soat_code and entry is None:
                raise ValueError(
                    f"El SOAT indicado no existe en el catálogo tarifario {reference_year}."
                )
            if selected_soat_code and selected_candidate is None:
                raise ValueError("El SOAT seleccionado no pertenece a los candidatos trazables.")
            if entry is not None and effective_manual_group and entry.surgical_group != effective_manual_group:
                raise ValueError("El código SOAT y el grupo quirúrgico indicados no coinciden.")
            if entry is None and effective_manual_group:
                group_entries = [
                    candidate
                    for candidate in catalog.entries
                    if candidate.surgical_group == effective_manual_group
                ]
                if not group_entries:
                    raise ValueError("El grupo quirúrgico no pertenece a los grupos oficiales de la vigencia.")
                signatures = {
                    tuple(sorted(tariff_entry_values(candidate, catalog.unit_value)[1].items()))
                    for candidate in group_entries
                }
                if len(signatures) != 1:
                    raise ValueError("El grupo quirúrgico no tiene una firma tarifaria única en el catálogo.")
                entry = group_entries[0]
            if entry is None:
                raise ValueError("No fue posible resolver la referencia SOAT en el catálogo tarifario.")
            base, components = tariff_entry_values(entry, catalog.unit_value)
            source_reference = (
                selected_candidate.reference
                if selected_candidate is not None
                else "; ".join(source.page_table for source in catalog.generated_from)
            )
            tariff_version = catalog.version
            crosswalk_version = JsonCupsSoatCrosswalk().load().version if requested_soat_code else ""
            selected_valuation = SoatCurationValuation(
                year=reference_year,
                soat_code=requested_soat_code or "",
                surgical_group=effective_manual_group or entry.surgical_group,
                base_tariff=base,
                components=components,
                catalog_version=tariff_version,
                source_reference=source_reference,
                selected_components=effective_components or list(components),
                formula="pendiente_calculo_conjunto",
                status="pendiente_revision",
                evidence=[
                    (
                        f"SOAT exacto {entry.code} validado contra catálogo {reference_year}."
                        if requested_soat_code
                        else f"Grupo {effective_manual_group} validado por firma tarifaria oficial."
                    )
                ],
            )
        updated = apply_curation_decision(
            curation,
            item_id=item_id,
            decision=CurationDecisionType(decision),
            actor=username,
            expected_version=expected_version,
            corrected_code=corrected_code,
            corrected_description=corrected_description,
            reason=reason,
            selected_soat_code=selected_soat_code,
            crosswalk_version=crosswalk_version,
            tariff_catalog_version=tariff_version,
            source_reference=source_reference,
            selected_soat_valuation=selected_valuation,
            calculate_reference=calculate_reference,
            manual_soat_code=effective_manual_soat,
            manual_surgical_group=effective_manual_group,
            selected_components=effective_components,
        )
        context.update(curation_context_payload(updated))
        self.cache_repository.upsert(
            username=username,
            case_key=case_key,
            context=context,
            regen_requested=False,
        )
        self.audit_logger.business_event(
            event_type="epicrisis.curation_decision",
            action="update_case_curation_item",
            outcome="success",
            service="case_epicrisis_service",
            resource={"case_key": case_key, "item_id": item_id, "decision": decision},
        )
        return dump_curation_model(updated)
