"""Repositorios MongoDB para la carga individual."""

from __future__ import annotations

from datetime import UTC, datetime
from typing import Any

from bson import ObjectId

from app.core.logging import InstrumentedMongoCollection, bind_log_context, get_audit_logger
from app.core.mongo_runtime import get_mongo_runtime
from app.individual_ingestion.domain.models import (
    ACTIVE_SESSION_STATUSES,
    NON_TERMINAL_UPLOAD_STATUSES,
    UPLOAD_STATUS_ELIMINADO,
)


class _EphemeralCollection:
    def create_index(self, *args: Any, **kwargs: Any) -> None:
        return None

    def insert_one(self, payload: dict[str, Any]):
        raise RuntimeError("Colección efímera no soporta escrituras persistentes.")

    def update_one(self, *args: Any, **kwargs: Any):
        return None

    def find_one(self, *args: Any, **kwargs: Any):
        return None


def _serialize_document(doc: dict[str, Any] | None) -> dict[str, Any] | None:
    if not doc:
        return None
    serialized = dict(doc)
    if "_id" in serialized:
        serialized["_id"] = str(serialized["_id"])
    return serialized


class MongoIndividualUploadRepository:
    def __init__(self, database: Any | None = None, file_store: Any | None = None) -> None:
        db = database if database is not None else get_mongo_runtime().sync_database
        collection = None
        if hasattr(db, "__getitem__"):
            try:
                collection = db["individual_ingestion_uploads"]
            except KeyError:
                collection = _EphemeralCollection()
        if collection is None:
            collection = _EphemeralCollection()
        self.collection = InstrumentedMongoCollection(collection, service="individual_upload_repository")
        self.audit_logger = get_audit_logger()
        self.file_store = file_store
        self.ensure_indexes()

    def attach_file_store(self, file_store: Any) -> None:
        self.file_store = file_store

    def ensure_indexes(self) -> None:
        self.collection.create_index([("usuario", 1), ("updated_at", -1)])
        self.collection.create_index([("usuario", 1), ("source_file_hash", 1), ("updated_at", -1)])
        self.collection.create_index([("status", 1), ("updated_at", -1)])
        self.collection.create_index([("usuario", 1), ("session_id", 1), ("updated_at", -1)])
        self.collection.create_index([("base_upload_id", 1), ("updated_at", -1)])

    def create_upload(self, payload: dict[str, Any]) -> str:
        inserted = self.collection.insert_one(payload)
        upload_id = str(inserted.inserted_id)
        bind_log_context(file_id=upload_id, username=payload.get("usuario", ""))
        self.audit_logger.business_event(
            event_type="individual.upload_created",
            action="create_upload",
            outcome="success",
            service="individual_upload_repository",
            resource={"upload_id": upload_id, "document_type": payload.get("selected_document_type", "")},
        )
        return upload_id

    def update_upload(self, upload_id: str, payload: dict[str, Any]) -> None:
        if not ObjectId.is_valid(upload_id):
            return
        self.collection.update_one({"_id": ObjectId(upload_id)}, {"$set": payload})

    def update_upload_if_status(
        self,
        upload_id: str,
        expected_statuses: set[str],
        payload: dict[str, Any],
    ) -> bool:
        if not ObjectId.is_valid(upload_id) or not expected_statuses:
            return False
        result = self.collection.update_one(
            {
                "_id": ObjectId(upload_id),
                "status": {"$in": sorted(expected_statuses)},
            },
            {"$set": payload},
        )
        return bool(getattr(result, "matched_count", 0))

    def claim_retry(self, upload_id: str, username: str, payload: dict[str, Any]) -> bool:
        if not ObjectId.is_valid(upload_id):
            return False
        result = self.collection.update_one(
            {
                "_id": ObjectId(upload_id),
                "usuario": str(username or "").strip(),
                "status": "fallido",
            },
            {
                "$set": payload,
                "$inc": {"retry_count": 1},
            },
        )
        return bool(getattr(result, "modified_count", 0))

    def get_upload(self, upload_id: str) -> dict[str, Any] | None:
        if not ObjectId.is_valid(upload_id):
            return None
        return _serialize_document(self.collection.find_one({"_id": ObjectId(upload_id)}))

    def find_latest_active(self, username: str) -> dict[str, Any] | None:
        return _serialize_document(
            self.collection.find_one(
                {"usuario": username, "status": {"$in": sorted(NON_TERMINAL_UPLOAD_STATUSES)}},
                sort=[("updated_at", -1)],
            )
        )

    def find_latest_by_hash(self, username: str, source_file_hash: str) -> dict[str, Any] | None:
        if not source_file_hash:
            return None
        return _serialize_document(
            self.collection.find_one(
                {"usuario": username, "source_file_hash": source_file_hash},
                sort=[("updated_at", -1)],
            )
        )

    def invalidate_by_clinical_document_id(
        self,
        *,
        username: str,
        clinical_document_id: str,
        reason: str,
        document_id: str = "",
        case_key: str = "",
        deleted_by: str = "",
    ) -> int:
        return self._invalidate_matching_uploads(
            self.collection.find(
                {
                    "usuario": username,
                    "clinical_document_id": str(clinical_document_id or "").strip(),
                }
            ),
            reason=reason,
            document_id=document_id,
            case_key=case_key,
            deleted_by=deleted_by or username,
        )

    def invalidate_by_case_key(
        self,
        *,
        username: str,
        case_key: str,
        reason: str,
        document_id: str = "",
        deleted_by: str = "",
    ) -> int:
        normalized_case_key = str(case_key or "").strip()
        if not normalized_case_key:
            return 0
        matching = (
            item
            for item in self.collection.find({"usuario": username})
            if normalized_case_key
            in {
                str(item.get("case_key") or "").strip(),
                str(item.get("provided_case_key") or "").strip(),
                str(item.get("session_case_key") or "").strip(),
            }
        )
        return self._invalidate_matching_uploads(
            matching,
            reason=reason,
            document_id=document_id,
            case_key=normalized_case_key,
            deleted_by=deleted_by or username,
        )

    def invalidate_by_batch_file_id(
        self,
        *,
        username: str,
        batch_file_id: str,
        reason: str,
        deleted_by: str = "",
    ) -> int:
        normalized_batch_file_id = str(batch_file_id or "").strip()
        if not normalized_batch_file_id:
            return 0
        return self._invalidate_matching_uploads(
            self.collection.find(
                {"usuario": username, "batch_file_id": normalized_batch_file_id}
            ),
            reason=reason,
            deleted_by=deleted_by or username,
        )

    def _invalidate_matching_uploads(
        self,
        records,
        *,
        reason: str,
        document_id: str = "",
        case_key: str = "",
        deleted_by: str,
    ) -> int:
        invalidated = 0
        timestamp = datetime.now(UTC).isoformat()
        for record in records:
            if str(record.get("status") or "").strip() == UPLOAD_STATUS_ELIMINADO:
                continue
            upload_id = record.get("_id")
            if upload_id is None:
                continue
            if self.file_store is not None:
                self.file_store.delete_file(str(record.get("stored_path") or ""))
            result = self.collection.update_one(
                {"_id": upload_id, "status": {"$ne": UPLOAD_STATUS_ELIMINADO}},
                {
                    "$set": {
                        "status": UPLOAD_STATUS_ELIMINADO,
                        "deleted_at": timestamp,
                        "deletion_reason": str(reason or "deleted").strip(),
                        "deletion_document_id": str(document_id or "").strip(),
                        "deletion_case_key": str(case_key or "").strip(),
                        "deleted_by": str(deleted_by or "").strip(),
                        "stored_path": "",
                        "extracted_text": "",
                        "pending_actions": [],
                        "warnings": [],
                        "contradictions": [],
                        "impact_summary": [],
                        "failure": {},
                        "can_retry": False,
                        "error": "",
                        "audit_only": True,
                        "updated_at": timestamp,
                    }
                },
            )
            invalidated += int(getattr(result, "matched_count", 0) or 0)
        return invalidated

    def get_active_session(self, username: str) -> dict[str, Any] | None:
        sessions = self.list_sessions(username)
        return sessions[0] if sessions else None

    def get_session(self, username: str, session_id: str) -> dict[str, Any] | None:
        normalized_session_id = str(session_id or "").strip()
        if not normalized_session_id:
            return None
        latest = _serialize_document(
            self.collection.find_one(
                {
                    "usuario": username,
                    "session_id": normalized_session_id,
                },
                sort=[("updated_at", -1)],
            )
        )
        if not latest:
            return None
        uploads = self.list_session_uploads(username, normalized_session_id)
        if not uploads:
            return None
        return self._build_session_view(latest=latest, uploads=uploads)

    def list_sessions(self, username: str, *, include_terminal: bool = False) -> list[dict[str, Any]]:
        query: dict[str, Any] = {
            "usuario": username,
            "session_id": {"$exists": True, "$ne": ""},
        }
        if not include_terminal:
            query["session_status"] = {"$in": sorted(ACTIVE_SESSION_STATUSES)}
        docs = list(self.collection.find(query).sort([("updated_at", -1), ("created_at", -1)]))
        latest_by_session: dict[str, dict[str, Any]] = {}
        for document in docs:
            serialized = _serialize_document(document)
            if serialized is None:
                continue
            session_id = str(serialized.get("session_id") or "").strip()
            if not session_id or session_id in latest_by_session:
                continue
            latest_by_session[session_id] = serialized

        sessions: list[dict[str, Any]] = []
        for session_id, latest in latest_by_session.items():
            uploads = self.list_session_uploads(username, session_id)
            if not uploads:
                continue
            sessions.append(self._build_session_view(latest=latest, uploads=uploads))
        sessions.sort(key=lambda item: str(item.get("updated_at") or ""), reverse=True)
        return sessions

    def list_session_uploads(self, username: str, session_id: str) -> list[dict[str, Any]]:
        normalized_session_id = str(session_id or "").strip()
        if not normalized_session_id:
            return []
        cursor = self.collection.find(
            {"usuario": username, "session_id": normalized_session_id},
            sort=[("created_at", 1), ("updated_at", 1)],
        )
        return [serialized for document in cursor if (serialized := _serialize_document(document))]

    def list_dependent_uploads(self, username: str, base_upload_id: str) -> list[dict[str, Any]]:
        normalized_base_upload_id = str(base_upload_id or "").strip()
        if not normalized_base_upload_id:
            return []
        cursor = self.collection.find(
            {"usuario": username, "depends_on_upload_id": normalized_base_upload_id},
            sort=[("created_at", 1), ("updated_at", 1)],
        )
        return [serialized for document in cursor if (serialized := _serialize_document(document))]

    def _build_session_view(self, *, latest: dict[str, Any], uploads: list[dict[str, Any]]) -> dict[str, Any]:
        session_id = str(latest.get("session_id") or "").strip()
        base_upload_id = str(latest.get("base_upload_id") or "").strip()
        if not base_upload_id:
            for item in uploads:
                if str(item.get("base_upload_id") or "").strip() == str(item.get("_id") or "").strip():
                    base_upload_id = str(item.get("_id") or "").strip()
                    break
        base_upload = next((item for item in uploads if str(item.get("_id") or "") == base_upload_id), None)
        active_case_key = str(latest.get("session_case_key") or latest.get("case_key") or "").strip()
        if not active_case_key and base_upload:
            active_case_key = str(base_upload.get("case_key") or "").strip()
        return {
            "session_id": session_id,
            "active_case_key": active_case_key,
            "base_upload_id": base_upload_id,
            "session_status": str(latest.get("session_status") or "").strip(),
            "base_ready": bool(base_upload and str(base_upload.get("status") or "").strip() == "completado"),
            "blocked_by_base_failure": bool(
                base_upload
                and str(base_upload.get("status") or "").strip()
                in {"blocked_base_failed", "fallido", "cancelado"}
            ),
            "updated_at": str(latest.get("updated_at") or latest.get("created_at") or "").strip(),
            "uploads": uploads,
        }
