import os
import pytest
import httpx
from unittest.mock import AsyncMock, MagicMock, patch
from dot_search.embed import make_openai_compatible_embed_fn, get_embedder


def make_mock_response(texts: list[str]) -> dict:
    return {
        "data": [
            {"index": i, "embedding": [float(i), 0.0, 0.0]}
            for i in range(len(texts))
        ]
    }


@pytest.fixture
def embedder():
    return make_openai_compatible_embed_fn(
        api_key="sk-test",
        model="text-embedding-3-small",
        base_url="https://api.openai.com/v1",
        dimension=1536,
    )


async def test_returns_one_vector_per_text(embedder):
    texts = ["hello", "world", "foo"]
    mock_response = AsyncMock()
    mock_response.json = lambda: make_mock_response(texts)
    mock_response.raise_for_status = lambda: None

    with patch("httpx.AsyncClient.post", return_value=mock_response):
        result = await embedder.fn(texts)

    assert len(result) == 3
    assert all(isinstance(v, list) for v in result)


async def test_vectors_are_ordered_by_index(embedder):
    texts = ["a", "b", "c"]
    # Return embeddings in reverse index order
    mock_response = AsyncMock()
    mock_response.json = lambda: {
        "data": [
            {"index": 2, "embedding": [2.0]},
            {"index": 0, "embedding": [0.0]},
            {"index": 1, "embedding": [1.0]},
        ]
    }
    mock_response.raise_for_status = lambda: None

    with patch("httpx.AsyncClient.post", return_value=mock_response):
        result = await embedder.fn(texts)

    assert result[0] == [0.0]
    assert result[1] == [1.0]
    assert result[2] == [2.0]


async def test_raises_on_http_error(embedder):
    mock_response = AsyncMock()
    mock_response.raise_for_status = lambda: (_ for _ in ()).throw(
        httpx.HTTPStatusError("500", request=None, response=None)
    )

    with patch("httpx.AsyncClient.post", return_value=mock_response):
        with pytest.raises(httpx.HTTPStatusError):
            await embedder.fn(["hello"])


async def test_sends_correct_payload(embedder):
    texts = ["test text"]
    mock_response = AsyncMock()
    mock_response.json = lambda: make_mock_response(texts)
    mock_response.raise_for_status = lambda: None

    with patch("httpx.AsyncClient.post", return_value=mock_response) as mock_post:
        await embedder.fn(texts)

    call_kwargs = mock_post.call_args
    payload = call_kwargs.kwargs["json"]
    assert payload["input"] == texts
    assert payload["model"] == "text-embedding-3-small"


def test_get_embedder_with_default_provider():
    """Test that provider='default' reads from environment variables."""
    os.environ["DOT__EMBED_API_KEY"] = "sk-test"
    os.environ["DOT__EMBED_BASE_URL"] = "https://api.openai.com/v1"
    os.environ["DOT__EMBED_MODEL"] = "text-embedding-3-small"
    os.environ["DOT__EMBED_DIMENSION"] = "1536"

    embedder = get_embedder("default", "ignored-model")

    assert embedder.dimension == 1536
    assert embedder.fn is not None


def test_get_embedder_with_openai_provider():
    """Test that provider='openai' creates an OpenAI-compatible embedder."""
    os.environ["DOT__EMBED_API_KEY"] = "sk-test"
    os.environ["DOT__EMBED_BASE_URL"] = "https://api.openai.com/v1"
    os.environ["DOT__EMBED_DIMENSION"] = "1536"

    embedder = get_embedder("openai", "text-embedding-3-large")

    assert embedder.dimension == 1536
    assert embedder.fn is not None


def test_get_embedder_with_unknown_provider():
    """Test that unknown providers raise ValueError."""
    with pytest.raises(ValueError, match="Unknown provider"):
        get_embedder("unknown", "model")


def test_get_embedder_openai_missing_env_vars():
    """Test that missing env vars for openai provider raise KeyError."""
    # Clear environment
    os.environ.pop("DOT__EMBED_API_KEY", None)
    os.environ.pop("DOT__EMBED_BASE_URL", None)
    os.environ.pop("DOT__EMBED_DIMENSION", None)

    with pytest.raises(KeyError):
        get_embedder("openai", "text-embedding-3-large")
