from __future__ import annotations

from datetime import datetime
from typing import Any

from pymongo.errors import OperationFailure

from app.case_epicrisis.application.utils import (
    is_compatible_cached_context,
    normalize_context_payload,
)
from app.core.logging import bind_log_context, get_audit_logger


class MongoCaseDocumentsReader:
    def __init__(self, mongo_analyses: Any):
        self.mongo_analyses = mongo_analyses
        self.audit_logger = get_audit_logger()

    def get_case_documents(self, username: str, case_key: str) -> dict[str, Any]:
        return {
            "historia": self._find_latest(username, case_key, "historia_clinica"),
            "quirurgico": self._find_latest(username, case_key, "quirurgico"),
            "factura": self._find_latest(username, case_key, "factura"),
            "radiologia": self._find_all(username, case_key, "radiologia"),
            "laboratorio": self._find_all(username, case_key, "laboratorio"),
            "generico": self._find_all(username, case_key, "generico"),
        }

    def _find_latest(self, username: str, case_key: str, tipo_documento: str) -> dict[str, Any] | None:
        return self.mongo_analyses.collection.find_one(
            {
                "usuario": username,
                "case_key": case_key,
                "tipo_documento": tipo_documento,
            },
            sort=[("fecha_analisis", -1)],
        )

    def _find_all(self, username: str, case_key: str, tipo_documento: str) -> list[dict[str, Any]]:
        return list(
            self.mongo_analyses.collection.find(
                {
                    "usuario": username,
                    "case_key": case_key,
                    "tipo_documento": tipo_documento,
                },
                sort=[("fecha_analisis", -1)],
            )
        )


class MongoCaseEpicrisisCacheRepository:
    def __init__(self, mongo_analyses: Any, colombia_tz: Any):
        self.mongo_analyses = mongo_analyses
        self.colombia_tz = colombia_tz
        self.audit_logger = get_audit_logger()

    def ensure_indexes(self) -> None:
        self._deduplicate_case_caches()
        try:
            self.mongo_analyses.collection.create_index(
                [("usuario", 1), ("tipo_documento", 1), ("case_key", 1)],
                unique=True,
                partialFilterExpression={
                    "tipo_documento": "epicrisis_case_cache",
                    "case_key": {"$exists": True, "$type": "string"},
                },
            )
        except OperationFailure as exc:
            if getattr(exc, "code", None) not in {85, 86}:
                raise

    def get(self, username: str, case_key: str) -> dict[str, Any] | None:
        cache = self.mongo_analyses.collection.find_one(
            {
                "usuario": username,
                "tipo_documento": "epicrisis_case_cache",
                "case_key": case_key,
            },
            sort=[("fecha_analisis", -1)],
        )
        if not cache or not isinstance(cache.get("contexto"), dict):
            return None
        if not is_compatible_cached_context(cache["contexto"]):
            return None
        bind_log_context(username=username, case_key=case_key)
        self.audit_logger.business_event(
            event_type="epicrisis.cache_hit",
            action="get_cache",
            outcome="success",
            service="case_epicrisis_cache_repository",
            resource={"case_key": case_key},
        )
        hydrated = dict(cache)
        hydrated["contexto"] = normalize_context_payload(cache["contexto"])
        return hydrated

    def upsert(
        self,
        *,
        username: str,
        case_key: str,
        context: dict[str, Any],
        regen_requested: bool,
    ) -> None:
        normalized_context = normalize_context_payload(context)
        payload = {
            "usuario": username,
            "tipo_documento": "epicrisis_case_cache",
            "case_key": case_key,
            "nombre_paciente": normalized_context.get("nombre_paciente", "desconocido"),
            "historia_id": (normalized_context.get("historia") or {}).get("_id"),
            "quirurgico_id": (normalized_context.get("quirurgico") or {}).get("_id"),
            "factura_id": (normalized_context.get("factura") or {}).get("_id"),
            "fecha_analisis": datetime.now(self.colombia_tz),
            "regen_requested": bool(regen_requested),
            "contexto": normalized_context,
        }
        self.mongo_analyses.collection.update_one(
            {
                "usuario": username,
                "tipo_documento": "epicrisis_case_cache",
                "case_key": case_key,
            },
            {"$set": payload},
            upsert=True,
        )
        bind_log_context(username=username, case_key=case_key)
        self.audit_logger.business_event(
            event_type="epicrisis.cache_refresh",
            action="upsert_cache",
            outcome="success",
            service="case_epicrisis_cache_repository",
            resource={"case_key": case_key, "regen_requested": bool(regen_requested)},
        )

    def _deduplicate_case_caches(self) -> None:
        duplicates = self.mongo_analyses.collection.aggregate(
            [
                {
                    "$match": {
                        "tipo_documento": "epicrisis_case_cache",
                        "case_key": {"$exists": True, "$type": "string", "$ne": ""},
                    }
                },
                {
                    "$sort": {
                        "fecha_analisis": -1,
                        "_id": -1,
                    }
                },
                {
                    "$group": {
                        "_id": {
                            "usuario": "$usuario",
                            "case_key": "$case_key",
                            "tipo_documento": "$tipo_documento",
                        },
                        "ids": {"$push": "$_id"},
                        "count": {"$sum": 1},
                    }
                },
                {"$match": {"count": {"$gt": 1}}},
            ]
        )
        for item in duplicates:
            ids = list(item.get("ids") or [])
            if len(ids) < 2:
                continue
            self.mongo_analyses.collection.delete_many({"_id": {"$in": ids[1:]}})
