import asyncio
from pymongo import AsyncMongoClient, ReturnDocument
from app.config import settings

class InMemoryCursor:
    def __init__(self, docs):
        self.docs = docs
        self._limit = None
        self._sort_key = None
        self._sort_dir = 1

    def sort(self, key, direction=1):
        self._sort_key = key
        self._sort_dir = direction
        return self

    def limit(self, n):
        self._limit = n
        return self

    async def to_list(self, length=None):
        res = list(self.docs)
        if self._sort_key:
            res.sort(key=lambda x: x.get(self._sort_key, ""), reverse=(self._sort_dir == -1))
        if self._limit is not None and self._limit > 0:
            res = res[:self._limit]
        if length is not None and length > 0:
            res = res[:length]
        return res

    def __aiter__(self):
        res = list(self.docs)
        if self._sort_key:
            res.sort(key=lambda x: x.get(self._sort_key, ""), reverse=(self._sort_dir == -1))
        if self._limit is not None and self._limit > 0:
            res = res[:self._limit]
        self._iter = iter(res)
        return self

    async def __anext__(self):
        try:
            return next(self._iter)
        except StopIteration:
            raise StopAsyncIteration

def _doc_matches_filter(doc, filter_dict):
    for k, v in filter_dict.items():
        doc_val = doc.get(k)
        if isinstance(v, dict):
            if "$nin" in v and doc_val in v["$nin"]:
                return False
            if "$in" in v and doc_val not in v["$in"]:
                return False
            if "$ne" in v and doc_val == v["$ne"]:
                return False
        elif doc_val != v:
            return False
    return True

class InMemoryCollection:
    def __init__(self, name):
        self.name = name
        self.documents = []

    async def find_one(self, filter_dict):
        for doc in self.documents:
            if _doc_matches_filter(doc, filter_dict):
                return dict(doc)
        return None

    async def insert_one(self, doc):
        stored = dict(doc)
        self.documents.append(stored)
        class InsertResult:
            inserted_id = stored.get("_id", stored.get("siteId", stored.get("sessionId", "id")))
        return InsertResult()

    async def update_one(self, filter_dict, update_dict):
        target = await self.find_one(filter_dict)
        if target:
            # Find matching doc reference
            for doc in self.documents:
                if _doc_matches_filter(doc, filter_dict):
                    if "$set" in update_dict:
                        for k, v in update_dict["$set"].items():
                            doc[k] = v
                    if "$unset" in update_dict:
                        for k in update_dict["$unset"].keys():
                            doc.pop(k, None)
                    class UpdateResult:
                        modified_count = 1
                    return UpdateResult()
        class UpdateResult:
            modified_count = 0
        return UpdateResult()

    async def find_one_and_update(self, filter_dict, update_dict, return_document=None):
        for doc in self.documents:
            if _doc_matches_filter(doc, filter_dict):
                before_doc = dict(doc)
                if "$set" in update_dict:
                    for k, v in update_dict["$set"].items():
                        doc[k] = v
                if "$unset" in update_dict:
                    for k in update_dict["$unset"].keys():
                        doc.pop(k, None)
                after_doc = dict(doc)
                if return_document == ReturnDocument.AFTER:
                    return after_doc
                return before_doc
        return None

    def find(self, filter_dict=None):
        filter_dict = filter_dict or {}
        results = []
        for doc in self.documents:
            if _doc_matches_filter(doc, filter_dict):
                results.append(dict(doc))
        return InMemoryCursor(results)

    async def delete_many(self, filter_dict=None):
        filter_dict = filter_dict or {}
        if not filter_dict:
            self.documents.clear()
        else:
            self.documents = [
                d for d in self.documents
                if not all(d.get(k) == v for k, v in filter_dict.items())
            ]

class InMemoryDatabase:
    def __init__(self, seed_default: bool = True):
        self.collections = {}
        if seed_default:
            # Pre-seed default Super Admin account so dashboard login works seamlessly in fallback mode
            from app.utils.security import hash_password
            agents_coll = InMemoryCollection("agents")
            agents_coll.documents.append({
                "agentId": "agent_super_admin",
                "name": "Super Admin",
                "email": "alok.chaudhary2001@gmail.com",
                "passwordHash": hash_password("123456"),
                "role": "ADMIN",
                "status": "ACTIVE"
            })
            self.collections["agents"] = agents_coll

    def __getitem__(self, item):
        if item not in self.collections:
            self.collections[item] = InMemoryCollection(item)
        return self.collections[item]

class Database:
    client: AsyncMongoClient | None = None
    in_memory_db: InMemoryDatabase | None = None
    use_fallback: bool = False

db_helper = Database()

def get_database():
    if db_helper.use_fallback:
        if db_helper.in_memory_db is None:
            db_helper.in_memory_db = InMemoryDatabase()
        return db_helper.in_memory_db

    if db_helper.client is None:
        try:
            db_helper.client = AsyncMongoClient(
                settings.mongodb_uri,
                tlsAllowInvalidCertificates=True,
                tlsAllowInvalidHostnames=True,
                serverSelectionTimeoutMS=2000,
            )
        except Exception:
            db_helper.use_fallback = True
            db_helper.in_memory_db = InMemoryDatabase()
            return db_helper.in_memory_db

    return db_helper.client[settings.database_name]

async def init_database():
    """Initialize database connection on startup. Falls back to in-memory if MongoDB is unavailable."""
    try:
        db = get_database()
        # If using real MongoDB, verify connectivity
        if not db_helper.use_fallback and db_helper.client is not None:
            await db_helper.client.admin.command("ping")
    except Exception:
        db_helper.use_fallback = True
        db_helper.in_memory_db = InMemoryDatabase()

def close_database():
    if db_helper.client is not None:
        try:
            db_helper.client.close()
        except Exception:
            pass
        db_helper.client = None