from typing import List
from concurrent.futures import ThreadPoolExecutor

from analogai.foundation.common.design_patterns.singleton import Singleton
from analogai.foundation.common.logging.app_logger import app_logger
from analogai.foundation.common.enums.embedding_provider import EmbeddingProvider as EmbeddingProviderEnum
from analogai.foundation.infrastructure.embeddings.base import EmbeddingProvider
from analogai.foundation.infrastructure.embeddings.provider_factory import create_embedding_provider
from analogai.foundation.settings import config

class EmbeddingService(metaclass=Singleton):
    def __init__(self):
        embedding_type = str(config.get("embed.type", "local")).strip().lower()
        embedding_provider = str(config.get("embed.provider", "local")).strip().lower()
        embedding_model = config.get("embed.model", "") or None

        if embedding_type == "local":
            provider_enum = EmbeddingProviderEnum.LOCAL
        else:
            try:
                provider_enum = EmbeddingProviderEnum(embedding_provider)
            except ValueError as exc:
                allowed = ", ".join([p.value for p in EmbeddingProviderEnum])
                raise ValueError(
                    f"Unknown EMBEDDING_PROVIDER value. Use one of: {allowed}."
                ) from exc

        self._provider = create_embedding_provider(provider_enum, model_name=embedding_model)
        app_logger.info(
            "EmbeddingService initialized with type='%s' provider='%s'",
            embedding_type,
            provider_enum.value,
        )

    @property
    def dimension(self) -> int:
        dimension = self._provider.dimension
        if dimension is None:
            raise RuntimeError("Embedding dimension unavailable from provider.")
        return dimension

    def encode(self, text: str) -> List[float]:
        return self._provider.encode(text)

    def encode_batch(self, texts: List[str]) -> List[List[float]]:
        if not texts:
            return []
        if not self._parallel_enabled() or len(texts) == 1:
            return self._provider.encode_batch(texts)
        max_workers = self._parallel_workers(len(texts))
        app_logger.debug("EmbeddingService parallel batch enabled: workers=%s", max_workers)
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            results = list(executor.map(self._provider.encode, texts))
        return results

    def _parallel_enabled(self) -> bool:
        value = config.get("embed.parallel", False)
        return bool(value)

    def _parallel_workers(self, batch_size: int) -> int:
        value = config.get("embed.parallel_workers", 4)
        try:
            workers = int(value)
        except (TypeError, ValueError):
            workers = 4
        workers = max(1, workers)
        return min(workers, max(1, int(batch_size)))