"""Tests for embeddings API methods."""

from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest
from pydantic import BaseModel

from railtown.engine.client import Railengine
from railtown.engine.embeddings import search_vector_store


@pytest.fixture
def mock_client(sample_pat, sample_engine_id):
    """Create a mock Railengine client."""
    with patch("railtown.engine.embeddings.httpx.AsyncClient") as mock_client_class:
        mock_http_client = AsyncMock()
        mock_client_class.return_value = mock_http_client

        client = Railengine(pat=sample_pat, engine_id=sample_engine_id)
        client._client = mock_http_client
        yield client, mock_http_client


@pytest.mark.asyncio
async def test_search_vector_store_success(mock_client):
    """Test searching vector store successfully."""
    client, mock_http_client = mock_client

    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = [
        {"eventId": "event-1", "embeddings": [0.1, 0.2, 0.3]},
        {"eventId": "event-2", "embeddings": [0.4, 0.5, 0.6]},
    ]
    mock_http_client.post = AsyncMock(return_value=mock_response)

    results = []
    async for item in search_vector_store(
        client,
        engine_id=client.engine_id,
        vector_store="VectorStore1",
        query="apple",
    ):
        results.append(item)

    assert len(results) == 2
    assert results[0]["eventId"] == "event-1"
    mock_http_client.post.assert_called_once()


@pytest.mark.asyncio
async def test_search_vector_store_with_filter(mock_client):
    """Test searching vector store with filter function."""
    client, mock_http_client = mock_client

    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = [
        {"eventId": "event-1", "vectorStore": "VectorStore1"},
        {"eventId": "event-2", "vectorStore": "VectorStore2"},
    ]
    mock_http_client.post = AsyncMock(return_value=mock_response)

    def filter_fn(item):
        return item.get("vectorStore") == "VectorStore1"

    results = []
    async for item in search_vector_store(
        client,
        engine_id=client.engine_id,
        vector_store="VectorStore1",
        query="test",
        filter_fn=filter_fn,
    ):
        results.append(item)

    assert len(results) == 1
    assert results[0]["eventId"] == "event-1"


@pytest.mark.asyncio
async def test_search_vector_store_empty_results(mock_client):
    """Test searching vector store with empty results."""
    client, mock_http_client = mock_client

    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = []
    mock_http_client.post = AsyncMock(return_value=mock_response)

    results = []
    async for item in search_vector_store(
        client,
        engine_id=client.engine_id,
        vector_store="VectorStore1",
        query="nonexistent",
    ):
        results.append(item)

    assert len(results) == 0


@pytest.mark.asyncio
async def test_search_vector_store_handles_errors_gracefully(mock_client):
    """Test search_vector_store handles errors gracefully."""
    client, mock_http_client = mock_client

    error = httpx.HTTPStatusError("Server Error", request=MagicMock(), response=MagicMock())
    mock_http_client.post = AsyncMock(side_effect=error)

    results = []
    async for item in search_vector_store(
        client,
        engine_id=client.engine_id,
        vector_store="VectorStore1",
        query="test",
    ):
        results.append(item)

    # Should return empty iterable on error
    assert len(results) == 0


@pytest.mark.asyncio
async def test_search_vector_store_invalid_vector_store_raises(mock_client):
    """Test that invalid vector_store raises ValueError."""
    client, _ = mock_client
    with pytest.raises(ValueError, match="vector_store must be one of"):
        async for _ in search_vector_store(
            client,
            vector_store="VectorStore4",
            query="test",  # type: ignore[arg-type]
        ):
            pass


@pytest.mark.asyncio
async def test_search_vector_store_handles_request_error(mock_client):
    """Test search_vector_store handles RequestError (e.g. timeout, connection)."""
    client, mock_http_client = mock_client
    mock_http_client.post = AsyncMock(side_effect=httpx.RequestError("Connection failed"))
    results = [item async for item in search_vector_store(client, query="test")]
    assert len(results) == 0


@pytest.mark.asyncio
async def test_search_vector_store_non_list_response_returns_empty(mock_client):
    """Test that non-list API response yields empty results."""
    client, mock_http_client = mock_client
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"items": []}
    mock_http_client.post = AsyncMock(return_value=mock_response)
    results = [item async for item in search_vector_store(client, query="test")]
    assert len(results) == 0


@pytest.mark.asyncio
async def test_search_vector_store_with_model_deserialization(mock_client):
    """Test search_vector_store deserializes to Pydantic model when model provided."""
    client, mock_http_client = mock_client
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = [
        {"content": '{"food_name": "Apple", "calories": 95}'},
    ]
    mock_http_client.post = AsyncMock(return_value=mock_response)

    class FoodItem(BaseModel):
        food_name: str
        calories: int

    results = [item async for item in search_vector_store(client, query="apple", model=FoodItem)]
    assert len(results) == 1
    assert isinstance(results[0], FoodItem)
    assert results[0].food_name == "Apple"
    assert results[0].calories == 95


@pytest.mark.asyncio
async def test_search_vector_store_sends_correct_payload(mock_client, sample_engine_id):
    """Test search_vector_store sends correct endpoint and JSON body."""
    client, mock_http_client = mock_client
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = []
    mock_http_client.post = AsyncMock(return_value=mock_response)

    async for _ in search_vector_store(
        client,
        engine_id=sample_engine_id,
        vector_store="VectorStore2",
        query="banana",
    ):
        pass

    call_args = mock_http_client.post.call_args
    assert f"/api/Engine/{sample_engine_id}/Embeddings/Search" in call_args[0][0]
    assert call_args.kwargs["json"] == {
        "VectorStore": "VectorStore2",
        "Query": "banana",
    }


@pytest.mark.asyncio
async def test_search_vector_store_uses_explicit_engine_id(mock_client):
    """Test that explicit engine_id overrides client default."""
    client, mock_http_client = mock_client
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = []
    mock_http_client.post = AsyncMock(return_value=mock_response)

    custom_engine_id = "custom-engine-456"
    async for _ in search_vector_store(client, engine_id=custom_engine_id, query="test"):
        pass

    call_args = mock_http_client.post.call_args
    assert f"/api/Engine/{custom_engine_id}/Embeddings/Search" in call_args[0][0]
