"""Unit tests for AsyncIndex data plane operations."""

from __future__ import annotations

import pytest
import respx

from httpx import Response

from pinecone.async_index import AsyncIndex


pytestmark = pytest.mark.unit

HOST = "my-index-abc123.svc.pinecone.io"
API_KEY = "test-key"
BASE_URL = f"https://{HOST}"


def _make_index() -> AsyncIndex:
    return AsyncIndex(host=HOST, api_key=API_KEY)


class TestAsyncUpsert:
    """Tests for AsyncIndex.upsert()."""

    @respx.mock
    @pytest.mark.asyncio()
    async def test_upsert_basic(self):
        route = respx.post(f"{BASE_URL}/vectors/upsert").mock(
            return_value=Response(200, json={"upsertedCount": 2})
        )

        index = _make_index()
        result = await index.upsert(
            vectors=[
                {"id": "v1", "values": [0.1, 0.2]},
                {"id": "v2", "values": [0.3, 0.4]},
            ]
        )

        assert route.called
        assert result.upserted_count == 2

    @respx.mock
    @pytest.mark.asyncio()
    async def test_upsert_with_namespace(self):
        route = respx.post(f"{BASE_URL}/vectors/upsert").mock(
            return_value=Response(200, json={"upsertedCount": 1})
        )

        index = _make_index()
        await index.upsert(
            vectors=[{"id": "v1", "values": [0.1, 0.2]}],
            namespace="test-ns",
        )

        request = route.calls.last.request
        assert b'"namespace":"test-ns"' in request.content

    @respx.mock
    @pytest.mark.asyncio()
    async def test_upsert_sends_api_version_header(self):
        route = respx.post(f"{BASE_URL}/vectors/upsert").mock(
            return_value=Response(200, json={"upsertedCount": 1})
        )

        index = _make_index()
        await index.upsert(vectors=[{"id": "v1", "values": [0.1]}])

        request = route.calls.last.request
        assert request.headers["X-Pinecone-Api-Version"] == "2025-10"


class TestAsyncQuery:
    """Tests for AsyncIndex.query()."""

    @respx.mock
    @pytest.mark.asyncio()
    async def test_query_by_vector(self):
        route = respx.post(f"{BASE_URL}/query").mock(
            return_value=Response(
                200,
                json={
                    "matches": [
                        {"id": "v1", "score": 0.95, "metadata": {"key": "val"}},
                        {"id": "v2", "score": 0.80},
                    ],
                    "namespace": "",
                    "usage": {"readUnits": 5},
                },
            )
        )

        index = _make_index()
        result = await index.query(vector=[0.1, 0.2], top_k=2)

        assert route.called
        assert len(result.matches) == 2
        assert result.matches[0].id == "v1"
        assert result.matches[0].score == 0.95
        assert result.usage is not None
        assert result.usage.read_units == 5

    @respx.mock
    @pytest.mark.asyncio()
    async def test_query_by_id(self):
        route = respx.post(f"{BASE_URL}/query").mock(
            return_value=Response(200, json={"matches": [], "namespace": ""})
        )

        index = _make_index()
        await index.query(id="v1", top_k=5)

        request = route.calls.last.request
        assert b'"id":"v1"' in request.content


class TestAsyncFetch:
    """Tests for AsyncIndex.fetch()."""

    @respx.mock
    @pytest.mark.asyncio()
    async def test_fetch_basic(self):
        route = respx.get(f"{BASE_URL}/vectors/fetch").mock(
            return_value=Response(
                200,
                json={
                    "vectors": {
                        "v1": {"id": "v1", "values": [0.1, 0.2]},
                    },
                    "namespace": "",
                    "usage": {"readUnits": 1},
                },
            )
        )

        index = _make_index()
        result = await index.fetch(ids=["v1"])

        assert route.called
        assert len(result.vectors) == 1
        assert result.vectors["v1"].values == [0.1, 0.2]


class TestAsyncDelete:
    """Tests for AsyncIndex.delete()."""

    @respx.mock
    @pytest.mark.asyncio()
    async def test_delete_by_ids(self):
        route = respx.post(f"{BASE_URL}/vectors/delete").mock(return_value=Response(200, json={}))

        index = _make_index()
        result = await index.delete(ids=["v1", "v2"])

        assert route.called
        assert result is not None

    @respx.mock
    @pytest.mark.asyncio()
    async def test_delete_all(self):
        route = respx.post(f"{BASE_URL}/vectors/delete").mock(return_value=Response(200, json={}))

        index = _make_index()
        await index.delete(delete_all=True, namespace="test-ns")

        request = route.calls.last.request
        assert b'"deleteAll":true' in request.content


class TestAsyncUpdate:
    """Tests for AsyncIndex.update()."""

    @respx.mock
    @pytest.mark.asyncio()
    async def test_update_metadata(self):
        route = respx.post(f"{BASE_URL}/vectors/update").mock(
            return_value=Response(200, json={"matchedRecords": 1})
        )

        index = _make_index()
        result = await index.update(id="v1", set_metadata={"category": "updated"})

        assert route.called
        assert result.matched_records == 1

    @respx.mock
    @pytest.mark.asyncio()
    async def test_update_values(self):
        route = respx.post(f"{BASE_URL}/vectors/update").mock(
            return_value=Response(200, json={"matchedRecords": 1})
        )

        index = _make_index()
        result = await index.update(id="v1", values=[0.5, 0.6])

        assert route.called
        assert result.matched_records == 1


class TestAsyncList:
    """Tests for AsyncIndex.list()."""

    @respx.mock
    @pytest.mark.asyncio()
    async def test_list_basic(self):
        route = respx.get(f"{BASE_URL}/vectors/list").mock(
            return_value=Response(
                200,
                json={
                    "vectors": [{"id": "v1"}, {"id": "v2"}],
                    "namespace": "test-ns",
                },
            )
        )

        index = _make_index()
        result = await index.list(namespace="test-ns")

        assert route.called
        assert len(result.vectors) == 2
        assert result.vectors[0].id == "v1"

    @respx.mock
    @pytest.mark.asyncio()
    async def test_list_with_pagination(self):
        respx.get(f"{BASE_URL}/vectors/list").mock(
            return_value=Response(
                200,
                json={
                    "vectors": [{"id": "v3"}],
                    "pagination": {"next": "token-abc"},
                },
            )
        )

        index = _make_index()
        result = await index.list(limit=1)

        assert result.pagination_token == "token-abc"


class TestAsyncFetchByMetadata:
    """Tests for AsyncIndex.fetch_by_metadata()."""

    @respx.mock
    @pytest.mark.asyncio()
    async def test_fetch_by_metadata_basic(self):
        route = respx.post(f"{BASE_URL}/vectors/fetch_by_metadata").mock(
            return_value=Response(
                200,
                json={
                    "vectors": {
                        "v1": {
                            "id": "v1",
                            "values": [0.1, 0.2],
                            "metadata": {"genre": "comedy"},
                        },
                    },
                    "namespace": "",
                    "usage": {"readUnits": 2},
                },
            )
        )

        index = _make_index()
        result = await index.fetch_by_metadata(filter={"genre": "comedy"})

        assert route.called
        assert len(result.vectors) == 1
        assert result.vectors["v1"].metadata == {"genre": "comedy"}


class TestAsyncDescribeIndexStats:
    """Tests for AsyncIndex.describe_index_stats()."""

    @respx.mock
    @pytest.mark.asyncio()
    async def test_describe_index_stats(self):
        route = respx.post(f"{BASE_URL}/describe_index_stats").mock(
            return_value=Response(
                200,
                json={
                    "namespaces": {
                        "": {"vectorCount": 100},
                        "test-ns": {"vectorCount": 50},
                    },
                    "dimension": 384,
                    "indexFullness": 0.05,
                    "totalVectorCount": 150,
                    "metric": "cosine",
                    "vectorType": "dense",
                },
            )
        )

        index = _make_index()
        result = await index.describe_index_stats()

        assert route.called
        assert result.total_vector_count == 150
        assert result.dimension == 384
        assert result.index_fullness == 0.05
        assert result.metric == "cosine"
        assert len(result.namespaces) == 2
