"""
Generic MongoDB client & helpers for CuCu backend.

Đây là nơi dùng chung cho:
- Core memos logic (memos, attachments, inbox, user_settings...)
- Conversation history của chatbot
"""

from __future__ import annotations

import logging
import os
from datetime import datetime, timezone
from typing import Any

from bson import ObjectId
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase

logger = logging.getLogger(__name__)

# MongoDB Configuration - Load from config.py to ensure consistency
from config import MONGODB_URI, MONGODB_DB_NAME

# Collection names với prefix cuccu_
COLLECTION_MEMOS = "cuccu_memos"
COLLECTION_ATTACHMENTS = "cuccu_attachments"
COLLECTION_MEMO_RELATIONS = "cuccu_memo_relations"
COLLECTION_REACTIONS = "cuccu_reactions"
COLLECTION_MEMO_EMBEDDINGS = "cuccu_memo_embeddings"
COLLECTION_INBOX = "cuccu_inbox"
COLLECTION_USER_SETTINGS = "cuccu_user_settings"


class MongoDBClient:
    """Singleton MongoDB client cho toàn backend CuCu."""

    _instance: "MongoDBClient | None" = None
    _client: AsyncIOMotorClient | None = None
    _db: AsyncIOMotorDatabase | None = None

    def __new__(cls) -> "MongoDBClient":
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    async def connect(self) -> None:
        """Initialize MongoDB connection with connection pooling."""
        if self._client is None:
            try:
                if not MONGODB_URI:
                    raise ValueError("MONGODB_URI environment variable is required")
                
                # Connection pooling configuration from environment
                max_pool_size = int(os.getenv("MONGODB_MAX_POOL_SIZE", "50"))
                min_pool_size = int(os.getenv("MONGODB_MIN_POOL_SIZE", "10"))
                max_idle_time_ms = int(os.getenv("MONGODB_MAX_IDLE_TIME_MS", "45000"))
                
                self._client = AsyncIOMotorClient(
                    MONGODB_URI,
                    maxPoolSize=max_pool_size,
                    minPoolSize=min_pool_size,
                    maxIdleTimeMS=max_idle_time_ms,
                )
                self._db = self._client[MONGODB_DB_NAME]
                await self._client.admin.command("ping")
                logger.info(
                    "✅ Connected to MongoDB: %s (pool: min=%d, max=%d)",
                    MONGODB_DB_NAME,
                    min_pool_size,
                    max_pool_size,
                )
            except Exception as e:  # pragma: no cover - startup failure
                logger.error("❌ MongoDB connection failed: %s", e)
                raise

    async def close(self) -> None:
        """Close MongoDB connection."""
        if self._client:
            self._client.close()
            self._client = None
            self._db = None
            logger.info("MongoDB connection closed")

    @property
    def db(self) -> AsyncIOMotorDatabase:
        if self._db is None:
            raise RuntimeError("MongoDB not connected. Call connect() first.")
        return self._db

    # Collection helpers (không bắt buộc dùng, nhưng tiện)
    @property
    def memos(self):
        return self.db[COLLECTION_MEMOS]

    @property
    def attachments(self):
        return self.db[COLLECTION_ATTACHMENTS]

    @property
    def memo_relations(self):
        return self.db[COLLECTION_MEMO_RELATIONS]

    @property
    def reactions(self):
        return self.db[COLLECTION_REACTIONS]

    @property
    def memo_embeddings(self):
        return self.db[COLLECTION_MEMO_EMBEDDINGS]

    @property
    def inbox(self):
        return self.db[COLLECTION_INBOX]

    @property
    def user_settings(self):
        return self.db[COLLECTION_USER_SETTINGS]


# Singleton instance
mongodb_client = MongoDBClient()


def generate_uid() -> str:
    """Generate a unique ID for memos (similar to nanoid)."""
    import secrets

    return secrets.token_urlsafe(16)[:22]


def utc_now() -> datetime:
    """Get current UTC time."""
    return datetime.now(timezone.utc)


def serialize_doc(doc: dict[str, Any]) -> dict[str, Any] | None:
    """Convert MongoDB document to JSON-serializable dict."""
    if doc is None:
        return None
    result = dict(doc)
    if "_id" in result:
        result["_id"] = str(result["_id"])
    for key, value in result.items():
        if isinstance(value, datetime):
            result[key] = value.isoformat()
    return result


def parse_object_id(id_str: str) -> ObjectId | None:
    """Parse string to ObjectId, return None if invalid."""
    try:
        return ObjectId(id_str)
    except Exception:
        return None


async def create_indexes():
    """Create database indexes for performance optimization."""
    try:
        db = mongodb_client.db
        
        # Indexes for memos collection
        await db[COLLECTION_MEMOS].create_index([("user_id", 1), ("created_at", -1)])
        await db[COLLECTION_MEMOS].create_index([("user_id", 1), ("visibility", 1)])
        await db[COLLECTION_MEMOS].create_index([("tags", 1)])
        await db[COLLECTION_MEMOS].create_index([("created_at", -1)])
        
        # Indexes for memo relations
        await db[COLLECTION_MEMO_RELATIONS].create_index([("memo_id", 1)])
        await db[COLLECTION_MEMO_RELATIONS].create_index([("related_memo_id", 1)])
        
        # Indexes for reactions
        await db[COLLECTION_REACTIONS].create_index([("memo_id", 1)])
        await db[COLLECTION_REACTIONS].create_index([("user_id", 1)])
        
        # Indexes for memo embeddings
        await db[COLLECTION_MEMO_EMBEDDINGS].create_index([("memo_id", 1)])
        await db[COLLECTION_MEMO_EMBEDDINGS].create_index([("user_id", 1), ("date", -1)])
        
        # Indexes for inbox
        await db[COLLECTION_INBOX].create_index([("user_id", 1), ("created_at", -1)])
        
        # Indexes for user settings
        await db[COLLECTION_USER_SETTINGS].create_index([("user_id", 1)], unique=True)
        
        logger.info("✅ Database indexes created successfully")
    except Exception as e:
        logger.warning(f"⚠️  Error creating indexes (may already exist): {e}")


async def init_mongodb():
    """Call on app startup."""
    await mongodb_client.connect()
    await create_indexes()


async def close_mongodb():
    """Call on app shutdown."""
    await mongodb_client.close()


