"""
Meilisearch Search Service for CuCu Note.

Provides instant keyword search on memos without embeddings.
- Sync memo CRUD → Meilisearch index
- Search by keyword with typo tolerance
- Filter by creator_id for multi-tenant isolation
"""

from __future__ import annotations

import logging
import asyncio
import json
from datetime import datetime
from typing import Any

import meilisearch
from meilisearch.errors import MeilisearchApiError

from config import MEILI_URL, MEILI_MASTER_KEY

logger = logging.getLogger(__name__)

INDEX_NAME = "memos"


class MeiliService:
    """Singleton Meilisearch service for memo search."""

    _instance: MeiliService | None = None
    _client: meilisearch.Client | None = None
    _enabled: bool = False

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

    async def initialize(self) -> bool:
        """Connect to Meilisearch and configure the memos index."""
        if not MEILI_URL or not MEILI_MASTER_KEY:
            logger.warning("⚠️ MEILI_URL or MEILI_MASTER_KEY not set — search disabled")
            self._enabled = False
            return False

        try:
            self._client = meilisearch.Client(MEILI_URL, MEILI_MASTER_KEY)

            # Test connection (wrapped in to_thread to avoid blocking event loop)
            await asyncio.to_thread(self._client.health)

            # Create or get index
            try:
                await asyncio.to_thread(self._client.create_index, INDEX_NAME, {"primaryKey": "id"})
                logger.info("✅ Created Meilisearch index: %s", INDEX_NAME)
            except MeilisearchApiError:
                pass  # Index already exists

            # Configure index settings
            index = self._client.index(INDEX_NAME)
            settings = {
                "searchableAttributes": ["content", "tags"],
                "filterableAttributes": ["creator_id", "visibility", "pinned", "row_status"],
                "sortableAttributes": ["created_at_ts"],
                "displayedAttributes": ["id", "uid", "content", "tags", "creator_id",
                                        "visibility", "pinned", "row_status",
                                        "created_at", "updated_at"],
            }
            await asyncio.to_thread(index.update_settings, settings)

            self._enabled = True
            logger.info("✅ Meilisearch connected: %s", MEILI_URL)
            return True

        except Exception as e:
            logger.warning("⚠️ Meilisearch unavailable: %s — search disabled", e)
            self._enabled = False
            return False

    @property
    def enabled(self) -> bool:
        return self._enabled

    def _memo_to_document(self, doc: dict[str, Any]) -> dict[str, Any]:
        """Convert memo doc (SQLite or Mongo-like) to Meilisearch document."""
        from common.sqlite_client import parse_time
        
        # Parse payload if it's a string (SQLite case)
        payload = doc.get("payload")
        if isinstance(payload, str):
            try:
                payload = json.loads(payload)
            except Exception:
                payload = {}
        elif not isinstance(payload, dict):
            payload = {}

        created_at = doc.get("created_at")
        updated_at = doc.get("updated_at")

        # Handle created_at timestamp for sorting
        created_at_ts = 0
        if created_at:
            if isinstance(created_at, datetime):
                created_at_ts = int(created_at.timestamp())
            elif isinstance(created_at, str):
                dt = parse_time(created_at)
                if dt:
                    created_at_ts = int(dt.timestamp())
            elif isinstance(created_at, (int, float)):
                created_at_ts = int(created_at)

        # Meilisearch needs a string id and numeric timestamp for sorting
        meili_doc = {
            "id": str(doc.get("id") or doc.get("_id")),
            "uid": doc.get("uid", ""),
            "content": doc.get("content", ""),
            "tags": payload.get("tags", []) if isinstance(payload, dict) else [],
            "creator_id": doc.get("creator_id", "anonymous"),
            "visibility": doc.get("visibility", "PRIVATE"),
            "pinned": bool(doc.get("pinned", False)),
            "row_status": doc.get("row_status", "NORMAL"),
            "created_at": str(created_at) if created_at else "",
            "updated_at": str(updated_at) if updated_at else "",
            "created_at_ts": created_at_ts,
        }
        return meili_doc

    def index_memo(self, doc: dict[str, Any]) -> None:
        """Add or update a memo in the search index."""
        if not self._enabled or not self._client:
            return
        try:
            meili_doc = self._memo_to_document(doc)
            self._client.index(INDEX_NAME).add_documents([meili_doc])
            logger.debug("✅ Indexed memo: %s", meili_doc["id"])
        except Exception as e:
            logger.warning("⚠️ Meili index error: %s", e)

    def delete_memo(self, memo_id: str) -> None:
        """Remove a memo from the search index."""
        if not self._enabled or not self._client:
            return
        try:
            self._client.index(INDEX_NAME).delete_document(memo_id)
            logger.debug("🗑️ Deleted memo from index: %s", memo_id)
        except Exception as e:
            logger.warning("⚠️ Meili delete error: %s", e)

    def search(
        self,
        query: str,
        creator_id: str | None = None,
        limit: int = 20,
    ) -> list[dict[str, Any]]:
        """
        Search memos by keyword.

        Returns list of matching memo documents from Meilisearch.
        """
        if not self._enabled or not self._client:
            return []

        try:
            search_params: dict[str, Any] = {
                "limit": limit,
                "sort": ["created_at_ts:desc"],
            }

            # Filter by creator for multi-tenant isolation
            filters = []
            if creator_id:
                filters.append(f'creator_id = "{creator_id}"')
            filters.append('row_status = "NORMAL"')
            search_params["filter"] = " AND ".join(filters)

            result = self._client.index(INDEX_NAME).search(query, search_params)
            return result.get("hits", [])

        except Exception as e:
            logger.warning("⚠️ Meili search error: %s", e)
            return []

    async def sync_all_memos(self) -> int:
        """
        Bulk index all memos from the database into Meilisearch.
        Called on startup to ensure index is up-to-date.
        Returns count of synced documents.
        """
        if not self._enabled or not self._client:
            return 0

        try:
            from common.mongo_client import mongodb_client

            # Since we moved to SQLite, $exists is not supported.
            # We fetch all parent memos (parent IS NULL or empty).
            rows = await mongodb_client.fetch_all(
                "SELECT * FROM cuccu_memos WHERE (parent IS NULL OR parent = '') AND row_status != 'ARCHIVED'"
            )
            docs = [dict(row) for row in rows]

            if not docs:
                logger.info("🔐 Meili sync: No memos found to index")
                return 0

            # Convert to Meilisearch documents
            meili_docs = [self._memo_to_document(doc) for doc in docs]
            
            # Bulk index documents (wrapped in to_thread to avoid blocking)
            index = self._client.index(INDEX_NAME)
            await asyncio.to_thread(index.add_documents, meili_docs)
            
            count = len(meili_docs)
            logger.info("✅ Meili sync complete: %d memos indexed", count)
            return count

        except Exception as e:
            logger.error("❌ Meili sync error: %s", e)
            return 0


# Singleton
meili_service = MeiliService()
