"""Configurable embedding engine with multiple backends."""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod

import numpy as np

logger = logging.getLogger(__name__)


class EmbeddingBackend(ABC):
    """Abstract base for embedding providers."""

    @property
    @abstractmethod
    def name(self) -> str:
        """Backend name."""

    @property
    @abstractmethod
    def dimension(self) -> int:
        """Embedding dimensionality."""

    @abstractmethod
    def embed(self, text: str) -> list[float]:
        """Embed one text input."""

    def embed_batch(self, texts: list[str]) -> list[list[float]]:
        """Embed many text inputs."""
        return [self.embed(t) for t in texts]


class SentenceTransformerBackend(EmbeddingBackend):
    """sentence-transformers embedding backend."""

    name = "sentence-transformers"

    def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
        from sentence_transformers import SentenceTransformer

        self._model = SentenceTransformer(model_name)
        self._dim = int(self._model.get_sentence_embedding_dimension())

    @property
    def dimension(self) -> int:
        return self._dim

    def embed(self, text: str) -> list[float]:
        return list(self._model.encode(text).tolist())

    def embed_batch(self, texts: list[str]) -> list[list[float]]:
        return [list(v) for v in self._model.encode(texts).tolist()]


class OllamaEmbeddingBackend(EmbeddingBackend):
    """Ollama embedding backend."""

    name = "ollama"

    def __init__(self, model: str = "nomic-embed-text", base_url: str = "http://127.0.0.1:11434"):
        from neurovault.cognitive.llm_client import OllamaClient

        self._client = OllamaClient(base_url=base_url)
        self._model = model
        self._dim = 768

    @property
    def dimension(self) -> int:
        return self._dim

    def embed(self, text: str) -> list[float]:
        return self._client.embed(text, model=self._model)


class TFIDFBackend(EmbeddingBackend):
    """Zero-dependency TF-IDF-like hash embedding backend."""

    name = "tfidf"

    def __init__(self, dimension: int = 384):
        self._dim = int(dimension)
        self._vocab: dict[str, int] = {}

    @property
    def dimension(self) -> int:
        return self._dim

    def embed(self, text: str) -> list[float]:
        words = text.lower().split()
        vec = np.zeros(self._dim, dtype=np.float32)
        for word in words:
            if word not in self._vocab:
                self._vocab[word] = hash(word) % self._dim
            vec[self._vocab[word]] += 1.0
        norm = np.linalg.norm(vec)
        if norm > 0:
            vec = vec / norm
        return vec.tolist()


class EmbeddingEngine:
    """Unified embedding engine with auto-detection of best backend."""

    def __init__(self, backend: EmbeddingBackend | None = None, config: dict | None = None):
        self._backend = backend or self._auto_detect(config or {})
        logger.info("Embedding backend: %s (dim=%d)", self._backend.name, self._backend.dimension)

    @property
    def dimension(self) -> int:
        return self._backend.dimension

    @property
    def backend_name(self) -> str:
        return self._backend.name

    def embed(self, text: str) -> list[float]:
        return self._backend.embed(text)

    def embed_batch(self, texts: list[str]) -> list[list[float]]:
        return self._backend.embed_batch(texts)

    def _auto_detect(self, config: dict) -> EmbeddingBackend:
        preferred = config.get("embedding_backend")
        if preferred == "ollama":
            try:
                return OllamaEmbeddingBackend()
            except Exception as exc:
                logger.debug("Preferred ollama backend unavailable: %s", exc)

        if preferred != "tfidf":
            try:
                model = str(config.get("embedding_model", "all-MiniLM-L6-v2"))
                return SentenceTransformerBackend(model)
            except Exception as exc:
                logger.debug("Sentence-transformers backend unavailable: %s", exc)

            try:
                backend = OllamaEmbeddingBackend()
                if backend._client.is_available():
                    return backend
            except Exception as exc:
                logger.debug("Ollama backend probe failed: %s", exc)

        return TFIDFBackend()
