"""
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"
COLLECTION_SHORTCUTS = "cuccu_shortcuts"
COLLECTION_TEAMS = "cuccu_teams"
COLLECTION_TEAM_MEMBERS = "cuccu_team_members"
COLLECTION_TEAM_MEMOS = "cuccu_team_memos"
COLLECTION_TEAM_COMMENTS = "cuccu_team_comments"
COLLECTION_TEAM_REACTIONS = "cuccu_team_reactions"
COLLECTION_USER_PROFILES = "cuccu_user_profiles"


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")
                
                # Use config values (sensible defaults for Atlas free tier)
                from config import MONGODB_MAX_POOL_SIZE, MONGODB_MIN_POOL_SIZE, MONGODB_MAX_IDLE_TIME_MS
                max_pool_size = MONGODB_MAX_POOL_SIZE or 10
                min_pool_size = MONGODB_MIN_POOL_SIZE or 2
                max_idle_time_ms = MONGODB_MAX_IDLE_TIME_MS or 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]

    @property
    def shortcuts(self):
        return self.db[COLLECTION_SHORTCUTS]

    @property
    def teams(self):
        return self.db[COLLECTION_TEAMS]

    @property
    def team_members(self):
        return self.db[COLLECTION_TEAM_MEMBERS]

    @property
    def team_memos(self):
        return self.db[COLLECTION_TEAM_MEMOS]

    @property
    def team_comments(self):
        return self.db[COLLECTION_TEAM_COMMENTS]

    @property
    def team_reactions(self):
        return self.db[COLLECTION_TEAM_REACTIONS]

    @property
    def cached_user_profiles(self):
        return self.db[COLLECTION_USER_PROFILES]


# 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 (Production-ready)."""
    try:
        db = mongodb_client.db
        
        # ====================== MEMOS COLLECTION ======================
        # Primary query patterns - creator_id is used extensively
        await db[COLLECTION_MEMOS].create_index([("creator_id", 1), ("created_at", -1)])
        await db[COLLECTION_MEMOS].create_index([("creator_id", 1), ("visibility", 1)])
        await db[COLLECTION_MEMOS].create_index([("creator_id", 1), ("pinned", 1)])
        await db[COLLECTION_MEMOS].create_index([("creator_id", 1), ("row_status", 1)])
        
        # Lookup by uid (used for memo detail pages)
        await db[COLLECTION_MEMOS].create_index([("uid", 1)], unique=True, sparse=True)
        
        # Comments lookup (parent reference)
        await db[COLLECTION_MEMOS].create_index([("parent", 1), ("visibility", 1)])
        
        # Tag filtering and date range queries
        await db[COLLECTION_MEMOS].create_index([("tags", 1)])
        await db[COLLECTION_MEMOS].create_index([("created_at", -1)])
        
        # ====================== MEMO RELATIONS ======================
        await db[COLLECTION_MEMO_RELATIONS].create_index([("memo_id", 1)])
        await db[COLLECTION_MEMO_RELATIONS].create_index([("related_memo_id", 1)])
        
        # ====================== REACTIONS ======================
        await db[COLLECTION_REACTIONS].create_index([("content_id", 1)])
        await db[COLLECTION_REACTIONS].create_index([("creator_id", 1)])
        
        # ====================== MEMO EMBEDDINGS ======================
        await db[COLLECTION_MEMO_EMBEDDINGS].create_index([("memo_id", 1)], unique=True, sparse=True)
        await db[COLLECTION_MEMO_EMBEDDINGS].create_index([("date_key", 1)])
        
        # ====================== INBOX ======================
        await db[COLLECTION_INBOX].create_index([("receiver", 1), ("created_at", -1)])
        await db[COLLECTION_INBOX].create_index([("receiver", 1), ("status", 1)])
        
        # ====================== USER SETTINGS ======================
        await db[COLLECTION_USER_SETTINGS].create_index([("user_id", 1)], unique=True)
        
        # ====================== ACTIVITIES ======================
        await db["cuccu_activities"].create_index([("created_at", -1)])
        await db["cuccu_activities"].create_index([("creator_id", 1)])
        
        # ====================== NOTIFICATIONS ======================
        await db["cuccu_notifications"].create_index([("recipient_id", 1), ("created_at", -1)])
        await db["cuccu_notifications"].create_index([("recipient_id", 1), ("status", 1)])
        
        # ====================== MEMO VERSIONS ======================
        await db["cuccu_memo_versions"].create_index([("memo_id", 1), ("version_index", -1)])
        
        # ====================== SHORTCUTS ======================
        await db[COLLECTION_SHORTCUTS].create_index([("creator_id", 1)])

        # ====================== TEAMS ======================
        await db[COLLECTION_TEAMS].create_index([("owner_id", 1)])
        await db[COLLECTION_TEAMS].create_index([("invite_code", 1)], unique=True)
        await db[COLLECTION_TEAM_MEMBERS].create_index([("team_id", 1), ("user_id", 1)], unique=True)
        await db[COLLECTION_TEAM_MEMBERS].create_index([("user_id", 1)])
        await db[COLLECTION_TEAM_MEMOS].create_index([("team_id", 1), ("space", 1), ("created_at", -1)])
        await db[COLLECTION_TEAM_MEMOS].create_index([("team_id", 1), ("creator_id", 1)])

        # ====================== TEAM COMMENTS ======================
        await db[COLLECTION_TEAM_COMMENTS].create_index([("memo_id", 1), ("created_at", 1)])
        await db[COLLECTION_TEAM_COMMENTS].create_index([("team_id", 1)])

        # ====================== TEAM REACTIONS ======================
        await db[COLLECTION_TEAM_REACTIONS].create_index([("memo_id", 1), ("emoji", 1)])
        await db[COLLECTION_TEAM_REACTIONS].create_index(
            [("memo_id", 1), ("user_id", 1), ("emoji", 1)], unique=True
        )

        # ====================== CACHED USER PROFILES ======================
        await db[COLLECTION_USER_PROFILES].create_index([("user_id", 1)], unique=True)
        # TTL: auto-delete after 24 hours
        await db[COLLECTION_USER_PROFILES].create_index(
            [("cached_at", 1)], expireAfterSeconds=86400
        )
        
        logger.info("✅ Database indexes created successfully (Production-ready)")
    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()


