"""Tests for services.embeddings — fastembed backend.

We don't actually load the fastembed model in tests (heavy download).
Instead, mock _get_model to return a fake that produces deterministic vectors.
"""
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from scribe.services.embeddings import (
    _cosine_similarity, get_embedding,
)


# ─── cosine_similarity (pure logic) ──────────────────────────────────────────


def test_cosine_similarity_orthogonal_is_zero():
    assert _cosine_similarity([1.0, 0.0], [0.0, 1.0]) == 0.0


def test_cosine_similarity_identical_is_one():
    assert _cosine_similarity([1.0, 0.0], [1.0, 0.0]) == pytest.approx(1.0)


def test_cosine_similarity_opposite_is_negative_one():
    assert _cosine_similarity([1.0, 0.0], [-1.0, 0.0]) == pytest.approx(-1.0)


def test_cosine_similarity_zero_length_safe():
    """Zero-magnitude vector must not divide-by-zero."""
    assert _cosine_similarity([0.0, 0.0], [1.0, 0.0]) == 0.0
    assert _cosine_similarity([1.0, 0.0], [0.0, 0.0]) == 0.0


def test_cosine_similarity_mismatched_dim_returns_zero():
    """Cross-migration safety: a 768-dim vs 384-dim comparison must not crash."""
    assert _cosine_similarity([1.0] * 5, [1.0] * 3) == 0.0


def test_cosine_similarity_empty_inputs():
    assert _cosine_similarity([], []) == 0.0
    assert _cosine_similarity([], [1.0]) == 0.0


# ─── get_embedding (fastembed path) ──────────────────────────────────────────


@pytest.mark.asyncio
async def test_get_embedding_returns_list_of_floats():
    """get_embedding wraps the embedder; the result is a Python list of floats."""
    fake_vec = MagicMock()
    fake_vec.tolist.return_value = [0.1, 0.2, 0.3, 0.4]
    fake_embedder = MagicMock()
    fake_embedder.embed = MagicMock(return_value=iter([fake_vec]))
    with patch(
        "scribe.services.embeddings._get_model",
        AsyncMock(return_value=fake_embedder),
    ):
        out = await get_embedding("hello world")
    assert out == [0.1, 0.2, 0.3, 0.4]
    fake_embedder.embed.assert_called_once_with(["hello world"])


@pytest.mark.asyncio
async def test_get_embedding_propagates_model_load_failures():
    """If fastembed can't initialize, the error propagates — callers catch
    and degrade to keyword search."""
    with patch(
        "scribe.services.embeddings._get_model",
        AsyncMock(side_effect=RuntimeError("model load failed")),
    ):
        with pytest.raises(RuntimeError, match="model load failed"):
            await get_embedding("x")
