"""Embedding service for generating text embeddings for RAG."""

from __future__ import annotations

import hashlib
from pathlib import Path
from typing import Any

import numpy as np

from ai_orchestrator.config.settings import Settings


class EmbeddingService:
    """
    Service for generating text embeddings.

    Supports multiple backends:
    1. OpenAI embeddings (production)
    2. Sentence-transformers (local, free)
    3. Simple hash-based fallback (for testing)
    """

    _cached_backend: str | None = None
    _cached_model: Any = None

    def __init__(self, settings: Settings) -> None:
        self.settings = settings
        self._backend: str | None = None
        self._model: Any = None
        self._initialize_backend()

    def _set_backend(self, backend: str, model: Any = None) -> None:
        self._backend = backend
        self._model = model
        self.__class__._cached_backend = backend
        self.__class__._cached_model = model

    def _initialize_backend(self) -> None:
        """Initialize the best available embedding backend."""
        configured_backend = getattr(self.settings, "embedding_backend", "auto")
        if configured_backend == "hash":
            self._set_backend("hash")
            return

        if configured_backend == "auto" and self.__class__._cached_backend is not None:
            self._backend = self.__class__._cached_backend
            self._model = self.__class__._cached_model
            return

        model_name = getattr(self.settings, "embedding_model_name", "all-MiniLM-L6-v2")
        allow_downloads = bool(getattr(self.settings, "allow_embedding_model_downloads", False))

        if configured_backend in {"auto", "sentence-transformers"}:
            try:
                from sentence_transformers import SentenceTransformer

                model = SentenceTransformer(model_name, local_files_only=not allow_downloads)
                self._set_backend("sentence-transformers", model)
                return
            except Exception:
                if configured_backend == "sentence-transformers":
                    self._set_backend("hash")
                    return

        if configured_backend in {"auto", "openai"}:
            try:
                import openai

                api_key = getattr(self.settings, "openai_api_key", None)
                if api_key:
                    self._set_backend("openai")
                    return
            except ImportError:
                if configured_backend == "openai":
                    self._set_backend("hash")
                    return

        self._set_backend("hash")

    def embed_text(self, text: str) -> list[float]:
        """Generate an embedding vector for the given text."""
        if self._backend == "sentence-transformers":
            return self._embed_sentence_transformers(text)
        elif self._backend == "openai":
            return self._embed_openai(text)
        else:
            return self._embed_hash(text)

    def embed_texts(self, texts: list[str]) -> list[list[float]]:
        """Generate embeddings for multiple texts (batch processing)."""
        if self._backend == "sentence-transformers":
            return self._embed_batch_sentence_transformers(texts)
        elif self._backend == "openai":
            return self._embed_batch_openai(texts)
        else:
            return [self._embed_hash(t) for t in texts]

    def embed_code(self, code: str, context: str = "") -> list[float]:
        """
        Generate an embedding for code with optional context.

        Creates a combined representation of code + context.
        """
        # Combine code and context for better representation
        combined = f"{context}\n\n```python\n{code}\n```" if context else code
        return self.embed_text(combined)

    def _embed_sentence_transformers(self, text: str) -> list[float]:
        """Embed using sentence-transformers."""
        embedding = self._model.encode(text, convert_to_numpy=True)
        return embedding.tolist()

    def _embed_batch_sentence_transformers(self, texts: list[str]) -> list[list[float]]:
        """Batch embed using sentence-transformers."""
        embeddings = self._model.encode(texts, convert_to_numpy=True)
        return embeddings.tolist()

    def _embed_openai(self, text: str) -> list[float]:
        """Embed using OpenAI's API."""
        import openai

        api_key = getattr(self.settings, "openai_api_key", None)
        client = openai.Client(api_key=api_key)
        response = client.embeddings.create(
            model="text-embedding-3-small",
            input=text
        )
        return response.data[0].embedding

    def _embed_batch_openai(self, texts: list[str]) -> list[list[float]]:
        """Batch embed using OpenAI's API."""
        import openai

        api_key = getattr(self.settings, "openai_api_key", None)
        client = openai.Client(api_key=api_key)
        response = client.embeddings.create(
            model="text-embedding-3-small",
            input=texts
        )
        return [item.embedding for item in response.data]

    def _embed_hash(self, text: str, dim: int = 384) -> list[float]:
        """
        Simple hash-based embedding (fallback for testing).

        Note: This is NOT a real semantic embedding. It's only for
        testing when no proper embedding model is available.
        """
        # Create a deterministic hash-based vector
        hash_obj = hashlib.sha256(text.encode())
        hash_bytes = hash_obj.digest()

        # Convert to a fixed-size vector
        result = []
        for i in range(dim):
            byte_idx = i % len(hash_bytes)
            # Normalize to [-1, 1] range
            val = (hash_bytes[byte_idx] - 128) / 128.0
            result.append(val)

        return result

    @property
    def embedding_dimension(self) -> int:
        """Get the dimension of the embedding vectors."""
        if self._backend == "sentence-transformers":
            return 384  # all-MiniLM-L6-v2 dimension
        elif self._backend == "openai":
            return 1536  # text-embedding-3-small dimension
        else:
            return 384  # Default for hash fallback

    @property
    def backend(self) -> str:
        """Get the current backend being used."""
        return self._backend or "none"

    def is_available(self) -> bool:
        """Check if a proper embedding backend is available."""
        return self._backend not in ("hash", "none")
