from __future__ import annotations

import json
from dataclasses import dataclass
from functools import lru_cache
from importlib import resources
from typing import Any, cast

from .registry import search_operations

np: Any
SentenceTransformer: Any
try:  # pragma: no cover - optional dependency
    import numpy as np
    from sentence_transformers import SentenceTransformer as _SentenceTransformer

    SentenceTransformer = _SentenceTransformer
    _VECTOR_AVAILABLE = True
except Exception:  # pragma: no cover - optional dependency
    np = None
    SentenceTransformer = None
    _VECTOR_AVAILABLE = False


@dataclass(frozen=True)
class EmbeddingIndex:
    operation_ids: list[str]
    matrix: Any
    model_name: str


def _load_embeddings() -> dict[str, Any]:
    data_file = resources.files("rabbitmq_mcp.data").joinpath("embeddings.json")
    if not data_file.is_file():
        raise FileNotFoundError("embeddings.json not found")
    return cast(dict[str, Any], json.loads(data_file.read_text(encoding="utf-8")))


@lru_cache
def _load_index() -> EmbeddingIndex:
    if not _VECTOR_AVAILABLE:
        raise RuntimeError("Vector search dependencies not available")
    payload = _load_embeddings()
    model_name = str(payload.get("model", ""))
    embeddings = payload.get("embeddings", {})
    operation_ids = list(embeddings.keys())
    matrix = np.array([embeddings[op] for op in operation_ids], dtype=float)
    return EmbeddingIndex(operation_ids=operation_ids, matrix=matrix, model_name=model_name)


@lru_cache
def _get_model() -> "SentenceTransformer":
    if not _VECTOR_AVAILABLE:
        raise RuntimeError("Vector search dependencies not available")
    index = _load_index()
    return SentenceTransformer(index.model_name)


def semantic_search(query: str, limit: int | None = 10) -> list[dict[str, Any]]:
    if not _VECTOR_AVAILABLE:
        return search_operations(query=query, limit=limit or 10)
    try:
        index = _load_index()
    except Exception:
        return search_operations(query=query, limit=limit or 10)
    if not query.strip():
        return search_operations(query=query, limit=limit or 10)
    model = _get_model()
    query_embedding = model.encode(query, normalize_embeddings=True)
    scores = index.matrix @ query_embedding
    ordered_indices = np.argsort(scores)[::-1]
    if limit is not None:
        ordered_indices = ordered_indices[:limit]
    return [
        {
            "operation_id": index.operation_ids[idx],
            "score": round(float(scores[idx]), 4),
        }
        for idx in ordered_indices
    ]
