# retoor <retoor@molodetz.nl>

from __future__ import annotations

import hashlib
import logging
import math
import re
import time
from dataclasses import dataclass, field

from devplacepy.config import INTERNAL_EMBED_MODEL, INTERNAL_EMBED_URL
from devplacepy.services.deepsearch.llm import gateway_client

logger = logging.getLogger(__name__)

EMBED_TIMEOUT_SECONDS = 60.0
LOCAL_EMBED_DIMS = 256
EMBED_CACHE_MAX = 5000
TOKEN_PATTERN = re.compile(r"[a-z0-9]+")


@dataclass
class EmbedResult:
    vectors: list[list[float]]
    backend: str
    latency_ms: int = 0
    cache_hits: int = 0

    @property
    def dims(self) -> int:
        for vector in self.vectors:
            if vector:
                return len(vector)
        return 0


@dataclass
class EmbeddingCache:
    store: dict[str, list[float]] = field(default_factory=dict)
    max_entries: int = EMBED_CACHE_MAX

    def key(self, text: str) -> str:
        return hashlib.sha1((text or "").encode("utf-8")).hexdigest()

    def get(self, text: str) -> list[float] | None:
        return self.store.get(self.key(text))

    def put(self, text: str, vector: list[float]) -> None:
        if not vector:
            return
        if len(self.store) >= self.max_entries:
            self.store.pop(next(iter(self.store)), None)
        self.store[self.key(text)] = vector


def _local_vector(text: str) -> list[float]:
    bucket = [0.0] * LOCAL_EMBED_DIMS
    tokens = TOKEN_PATTERN.findall((text or "").lower())
    if not tokens:
        return bucket
    for token in tokens:
        digest = hashlib.sha1(token.encode("utf-8")).digest()
        index = int.from_bytes(digest[:4], "big") % LOCAL_EMBED_DIMS
        sign = 1.0 if digest[4] % 2 == 0 else -1.0
        bucket[index] += sign
    norm = math.sqrt(sum(value * value for value in bucket))
    if norm == 0.0:
        return bucket
    return [value / norm for value in bucket]


def local_embed(texts: list[str]) -> EmbedResult:
    start = time.monotonic()
    vectors = [_local_vector(text) for text in texts]
    latency_ms = int((time.monotonic() - start) * 1000)
    return EmbedResult(vectors=vectors, backend="local", latency_ms=latency_ms)


async def embed_texts(
    texts: list[str],
    api_key: str,
    *,
    gateway_url: str = INTERNAL_EMBED_URL,
    cache: EmbeddingCache | None = None,
) -> EmbedResult:
    if not texts:
        return EmbedResult(vectors=[], backend="empty")
    cache_hits = 0
    pending_index: list[int] = []
    pending_text: list[str] = []
    resolved: list[list[float] | None] = [None] * len(texts)
    if cache is not None:
        for index, text in enumerate(texts):
            cached_vector = cache.get(text)
            if cached_vector is not None:
                resolved[index] = cached_vector
                cache_hits += 1
            else:
                pending_index.append(index)
                pending_text.append(text)
    else:
        pending_index = list(range(len(texts)))
        pending_text = list(texts)
    if not pending_text:
        return EmbedResult(
            vectors=[vector or [] for vector in resolved],
            backend="cache",
            latency_ms=0,
            cache_hits=cache_hits,
        )
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
        "X-App-Reference": "devplace-deepsearch-v-1-0-0",
    }
    payload = {"model": INTERNAL_EMBED_MODEL, "input": pending_text}
    start = time.monotonic()
    backend = "gateway"
    try:
        response = await gateway_client().post(
            gateway_url, json=payload, headers=headers, timeout=EMBED_TIMEOUT_SECONDS
        )
        if response.status_code >= 400:
            raise RuntimeError(f"embed gateway returned {response.status_code}")
        data = response.json()
        rows = data.get("data") or []
        fresh = [row.get("embedding") or [] for row in rows]
        if len(fresh) != len(pending_text) or any(not vector for vector in fresh):
            raise RuntimeError("embed gateway returned an incomplete response")
    except Exception as exc:
        logger.warning("deepsearch embedding gateway failed, using local: %s", exc)
        fresh = [_local_vector(text) for text in pending_text]
        backend = "local"
    latency_ms = int((time.monotonic() - start) * 1000)
    for offset, index in enumerate(pending_index):
        vector = fresh[offset]
        resolved[index] = vector
        if cache is not None:
            cache.put(pending_text[offset], vector)
    return EmbedResult(
        vectors=[vector or [] for vector in resolved],
        backend=backend,
        latency_ms=latency_ms,
        cache_hits=cache_hits,
    )
