"""Business logic API for mykb."""

import time
from dataclasses import dataclass, field

from mykb.backends import get_backend, BaseBackend
from mykb.backends.base import SearchResult
from mykb.core.config import get_config, get_backend_config
from mykb.core.embedder import Embedder
from mykb.core.indexer import index_collection, EMBEDDING_DIMENSION


@dataclass
class MykbAPI:
    """
    Main API class for mykb operations.

    Manages embedder and backend lifecycle.
    Can be used by CLI, daemon, or directly in code.
    """

    embedder: Embedder | None = None
    backend: BaseBackend | None = None
    _config: dict = field(default_factory=dict)

    def __post_init__(self):
        self._config = get_config()

    def ensure_backend(self) -> BaseBackend:
        """Lazy-load backend."""
        if self.backend is None:
            backend_type, backend_config = get_backend_config()
            self.backend = get_backend(backend_type, **backend_config)
            self.backend.initialize('mykb', EMBEDDING_DIMENSION)
        return self.backend

    def ensure_embedder(self) -> Embedder:
        """Lazy-load embedder."""
        if self.embedder is None:
            self.embedder = Embedder()
        return self.embedder

    # --- Embedding ---

    def embed_documents(self, texts: list[str]) -> list[list[float]]:
        """Encode document texts into embedding vectors."""
        embedder = self.ensure_embedder()
        return embedder.encode_documents(texts)

    def embed_query(self, text: str) -> list[float]:
        """Encode a search query into an embedding vector."""
        embedder = self.ensure_embedder()
        return embedder.encode_query(text)

    # --- Indexing ---

    def index(
        self,
        collection: str | None = None,
        full: bool = False,
        with_embeddings: bool = False,
    ) -> dict:
        """
        Index one or all collections.

        Returns: {"collection": str, "chunks": int, "elapsed": float}
        """
        backend = self.ensure_backend()
        embed_fn = None
        if with_embeddings:
            embedder = self.ensure_embedder()
            embed_fn = embedder.encode_documents

        t0 = time.perf_counter()

        chunks = index_collection(
            collection,
            backend,
            full=full,
            embed_fn=embed_fn,
        )

        elapsed = time.perf_counter() - t0
        return {
            'collection': collection or 'all',
            'chunks': chunks,
            'elapsed': elapsed,
        }

    def embed_collection(self, collection: str, full: bool = False) -> dict:
        """
        Generate/update embeddings for indexed documents.

        Returns: {"collection": str, "updated": int}
        """
        backend = self.ensure_backend()
        embedder = self.ensure_embedder()

        filter_str = f'collection = "{collection}"'
        offset = 0
        total_updated = 0
        limit = 1000

        while True:
            docs = backend.get_documents('mykb', limit=limit, offset=offset, filter=filter_str)
            if not docs:
                break

            to_embed = []
            for doc in docs:
                if full or not doc.get('_vectors'):
                    to_embed.append(doc)

            if to_embed:
                texts = [d['content'] for d in to_embed]
                vectors = embedder.encode_documents(texts)
                id_vectors = {d['id']: vec for d, vec in zip(to_embed, vectors)}
                if id_vectors:
                    backend.update_embeddings('mykb', id_vectors)
                    total_updated += len(id_vectors)

            offset += len(docs)
            if len(docs) < limit:
                break

        return {'collection': collection, 'updated': total_updated}

    # --- Search ---

    def search_bm25(
        self,
        query: str,
        limit: int = 10,
        collection: str | None = None,
    ) -> SearchResult:
        """BM25 full-text search (no embedding needed)."""
        backend = self.ensure_backend()
        filter_str = f'collection = "{collection}"' if collection else None
        return backend.bm25_search('mykb', query, limit=limit, filter=filter_str)

    def search_vector(
        self,
        query: str,
        limit: int = 10,
        collection: str | None = None,
    ) -> SearchResult:
        """Vector semantic search."""
        backend = self.ensure_backend()
        query_vector = self.embed_query(query)
        filter_str = f'collection = "{collection}"' if collection else None
        return backend.vector_search('mykb', query_vector, limit=limit, filter=filter_str)

    def search_hybrid(
        self,
        query: str,
        limit: int = 10,
        collection: str | None = None,
        semantic_ratio: float = 0.5,
    ) -> SearchResult:
        """Hybrid search (BM25 + vector)."""
        backend = self.ensure_backend()
        query_vector = self.embed_query(query)
        filter_str = f'collection = "{collection}"' if collection else None
        return backend.hybrid_search(
            'mykb',
            query,
            query_vector,
            limit=limit,
            semantic_ratio=semantic_ratio,
            filter=filter_str,
        )

    # --- Lifecycle ---

    def close(self):
        """Clean up resources."""
        if self.backend:
            self.backend.close()


# Singleton for CLI usage
_default_api: MykbAPI | None = None


def get_api() -> MykbAPI:
    """Get the default API instance (singleton for CLI)."""
    global _default_api
    if _default_api is None:
        _default_api = MykbAPI()
    return _default_api
