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"),
            "prescripcion": self._find_all(username, case_key, "prescripcion"),
        }

    def _find_latest(self, username: str, case_key: str, tipo_documento: str) -> dict[str, Any] | None:
        query = {
            "usuario": username,
            "case_key": case_key,
            "tipo_documento": tipo_documento,
        }
        if hasattr(self.mongo_analyses.collection, "find"):
            candidates = list(
                self.mongo_analyses.collection.find(
                    query,
                    sort=[("fecha_analisis", -1)],
                )
            )
            for candidate in candidates:
                if not self._is_low_confidence_document(candidate):
                    return candidate
            return candidates[0] if candidates else None
        return self.mongo_analyses.collection.find_one(query, 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)],
            )
        )

    @staticmethod
    def _is_low_confidence_document(document: dict[str, Any] | None) -> bool:
        if not isinstance(document, dict):
            return False
        quality = document.get("analysis_quality")
        if not isinstance(quality, dict):
            return False
        return bool(quality.get("low_confidence"))


class MongoCaseEpicrisisCacheRepository:
    def __init__(self, mongo_analyses: Any, colombia_tz: Any, catalog_registry: Any = None):
        self.mongo_analyses = mongo_analyses
        self.colombia_tz = colombia_tz
        self.catalog_registry = catalog_registry
        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
        if not self._catalog_fingerprints_match(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 _catalog_fingerprints_match(self, context: dict[str, Any]) -> bool:
        cached = context.get("catalogos_utilizados")
        if not cached or self.catalog_registry is None:
            return True
        current = {
            reference.system.value: reference.fingerprint
            for reference in self.catalog_registry.references()
        }
        cached_map = {
            str(item.get("system") or ""): str(item.get("fingerprint") or "")
            for item in cached
            if isinstance(item, dict)
        }
        return cached_map == current

    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:]}})
