from __future__ import annotations

import re
from typing import Any

from app.core.logging import bind_log_context, get_audit_logger
from app.rips.application.models import (
    GenerateCaseRipsCommand,
    GeneratedRipsPayload,
    RipsGenerationOptions,
    RipsOperationalTemplate,
    RipsTemplateScope,
)
from app.rips.application.use_cases import GenerateCaseRipsUseCase
from app.rips.infrastructure.providers import EpicrisisServiceCaseContextProvider
from app.rips.infrastructure.repositories import MongoRipsPayloadRepository


def _normalize_text(value: Any) -> str:
    return str(value or "").strip()


def _normalize_digits(value: Any) -> str:
    return re.sub(r"\D", "", str(value or ""))


class RipsService:
    """Fachada para generación versionada de RIPS basada en la epicrisis consolidada."""

    def __init__(
        self,
        *,
        mongo_analyses: Any,
        colombia_tz: Any,
        case_epicrisis_service: Any,
    ):
        self.audit_logger = get_audit_logger()
        self.repository = MongoRipsPayloadRepository(mongo_analyses, colombia_tz)
        self.context_provider = EpicrisisServiceCaseContextProvider(case_epicrisis_service)
        self.generate_case_rips_use_case = GenerateCaseRipsUseCase(
            context_provider=self.context_provider,
            repository=self.repository,
        )

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

    def generate_case_rips(
        self,
        username: str,
        case_key: str,
        *,
        options: dict[str, Any] | RipsGenerationOptions,
        regen: bool = False,
        persist: bool = True,
    ) -> GeneratedRipsPayload:
        bind_log_context(username=username, case_key=case_key, document_type="rips")
        command = GenerateCaseRipsCommand(
            username=username,
            case_key=case_key,
            options=options if isinstance(options, RipsGenerationOptions) else RipsGenerationOptions(**options),
            regen=regen,
            persist=persist,
        )
        result = self.generate_case_rips_use_case.execute(command)
        self.audit_logger.business_event(
            event_type="rips.generated" if result.source == "generated" else "rips.cache_hit",
            action="generate_case_rips",
            outcome="success",
            service="rips_service",
            resource={
                "case_key": case_key,
                "version": result.version,
                "regen": bool(regen),
                "stored": bool(persist),
                "validation_status": result.validation_report.status,
                "ready_for_cuv": result.validation_report.ready_for_cuv,
            },
        )
        return result

    def get_latest_case_rips(
        self,
        username: str,
        case_key: str,
    ) -> GeneratedRipsPayload | None:
        bind_log_context(username=username, case_key=case_key, document_type="rips")
        return self.generate_case_rips_use_case.get_latest(username, case_key)

    def download_case_rips(
        self,
        username: str,
        case_key: str,
    ) -> GeneratedRipsPayload | None:
        bind_log_context(username=username, case_key=case_key, document_type="rips")
        result = self.generate_case_rips_use_case.get_latest(username, case_key)
        if result is None:
            return None
        self.audit_logger.business_event(
            event_type="rips.downloaded",
            action="download_case_rips",
            outcome="success",
            service="rips_service",
            resource={
                "case_key": case_key,
                "version": result.version,
                "export_status": result.export_status,
                "ready_for_cuv": result.validation_report.ready_for_cuv,
            },
        )
        return result

    def _resolve_template_scope(self, context: dict[str, Any], level: str) -> RipsTemplateScope:
        factura = context.get("factura") or {}
        factura_json = factura.get("factura_json") if isinstance(factura, dict) else {}
        factura_json = factura_json if isinstance(factura_json, dict) else {}
        provider = factura_json.get("proveedor") if isinstance(factura_json.get("proveedor"), dict) else {}
        payer = factura_json.get("pagador") if isinstance(factura_json.get("pagador"), dict) else {}
        metadata = context.get("metadatos_hc") if isinstance(context.get("metadatos_hc"), dict) else {}

        scope = RipsTemplateScope(
            level=level,  # type: ignore[arg-type]
            provider_nit=_normalize_digits(provider.get("nit")) or None,
            provider_name=_normalize_text(provider.get("nombre")) or _normalize_text(metadata.get("prestador_servicio")) or None,
            payer_nit=_normalize_digits(payer.get("nit_pagador")) or None,
            payer_name=_normalize_text(payer.get("aseguradora_eps")) or None,
            site_name=_normalize_text(metadata.get("prestador_servicio")) or None,
        )
        if level == "global":
            return scope.model_copy(
                update={
                    "provider_nit": None,
                    "provider_name": None,
                    "payer_nit": None,
                    "payer_name": None,
                    "site_name": None,
                }
            )
        if level == "provider":
            return scope.model_copy(update={"payer_nit": None, "payer_name": None, "site_name": None})
        if level == "provider_payer":
            return scope.model_copy(update={"site_name": None})
        return scope

    def _scope_to_repository_dict(self, scope: RipsTemplateScope) -> dict[str, Any]:
        provider_nit = _normalize_digits(scope.provider_nit)
        payer_nit = _normalize_digits(scope.payer_nit)
        site_name = _normalize_text(scope.site_name).upper()
        if scope.level == "global":
            scope_key = "global"
        elif scope.level == "provider":
            scope_key = f"provider:{provider_nit}"
        elif scope.level == "provider_payer":
            scope_key = f"provider:{provider_nit}:payer:{payer_nit}"
        else:
            scope_key = f"provider:{provider_nit}:payer:{payer_nit}:site:{site_name}"
        return {
            "level": scope.level,
            "provider_nit": provider_nit or None,
            "provider_name": _normalize_text(scope.provider_name) or None,
            "payer_nit": payer_nit or None,
            "payer_name": _normalize_text(scope.payer_name) or None,
            "site_name": _normalize_text(scope.site_name) or None,
            "scope_key": scope_key,
        }

    def _scope_model_from_dict(self, payload: dict[str, Any]) -> RipsTemplateScope:
        return RipsTemplateScope(
            level=payload.get("level") or "global",
            provider_nit=payload.get("provider_nit"),
            provider_name=payload.get("provider_name"),
            payer_nit=payload.get("payer_nit"),
            payer_name=payload.get("payer_name"),
            site_name=payload.get("site_name"),
        )

    def _build_scope_candidates(self, context: dict[str, Any]) -> list[dict[str, Any]]:
        candidates: list[dict[str, Any]] = []
        for level in ("provider_payer_site", "provider_payer", "provider", "global"):
            scope = self._resolve_template_scope(context, level)
            scope_dict = self._scope_to_repository_dict(scope)
            if level == "provider_payer_site" and (
                not scope_dict.get("provider_nit") or not scope_dict.get("payer_nit") or not scope_dict.get("site_name")
            ):
                continue
            if level == "provider_payer" and (not scope_dict.get("provider_nit") or not scope_dict.get("payer_nit")):
                continue
            if level == "provider" and not scope_dict.get("provider_nit"):
                continue
            candidates.append(scope_dict)
        return candidates

    def get_case_template(self, username: str, case_key: str) -> RipsOperationalTemplate:
        bind_log_context(username=username, case_key=case_key, document_type="rips_template")
        context = self.context_provider.get_case_context(username, case_key, regen=False)
        scope_candidates = self._build_scope_candidates(context)
        document = self.repository.get_template(username, scope_candidates)
        if document is None:
            return RipsOperationalTemplate(
                scope=self._scope_model_from_dict(scope_candidates[0]) if scope_candidates else RipsTemplateScope(level="global"),
                options={},
                version=None,
                stored=False,
                source="global_default",
                resolved_from_level=None,
            )

        stored_scope = document.get("template_scope") if isinstance(document.get("template_scope"), dict) else {}
        return RipsOperationalTemplate(
            scope=self._scope_model_from_dict(stored_scope),
            options=document.get("options") or {},
            version=document.get("version"),
            stored=True,
            source="case_scope",
            resolved_from_level=stored_scope.get("level"),
        )

    def save_case_template(
        self,
        username: str,
        case_key: str,
        *,
        options: dict[str, Any] | RipsGenerationOptions,
        scope_level: str,
    ) -> RipsOperationalTemplate:
        bind_log_context(username=username, case_key=case_key, document_type="rips_template")
        context = self.context_provider.get_case_context(username, case_key, regen=False)
        scope = self._resolve_template_scope(context, scope_level)
        scope_dict = self._scope_to_repository_dict(scope)
        if scope_level == "provider" and not scope_dict.get("provider_nit"):
            raise ValueError("El caso no tiene NIT de prestador suficiente para guardar una plantilla por prestador.")
        if scope_level == "provider_payer" and (
            not scope_dict.get("provider_nit") or not scope_dict.get("payer_nit")
        ):
            raise ValueError("El caso no tiene prestador y pagador suficientes para guardar una plantilla compartida.")
        if scope_level == "provider_payer_site" and (
            not scope_dict.get("provider_nit") or not scope_dict.get("payer_nit") or not scope_dict.get("site_name")
        ):
            raise ValueError("El caso no tiene prestador, pagador y sede suficientes para guardar una plantilla por sede.")
        options_model = options if isinstance(options, RipsGenerationOptions) else RipsGenerationOptions(**options)
        stored = self.repository.save_template(
            username=username,
            scope=scope_dict,
            options=options_model.model_dump(mode="python", exclude_none=True),
        )
        self.audit_logger.business_event(
            event_type="rips.template_saved",
            action="save_case_rips_template",
            outcome="success",
            service="rips_service",
            resource={
                "case_key": case_key,
                "scope_level": scope_level,
                "version": stored.get("version"),
                "scope_key": scope_dict.get("scope_key"),
            },
        )
        return RipsOperationalTemplate(
            scope=self._scope_model_from_dict(scope_dict),
            options=options_model.model_dump(mode="python", exclude_none=True),
            version=stored.get("version"),
            stored=True,
            source="case_scope",
            resolved_from_level=scope_level,  # type: ignore[arg-type]
        )
