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

from __future__ import annotations

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


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) -> 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.ensure_indexes()

    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 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 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,
        }
