"""Unit tests for AsyncRecords sub-namespace operations."""

from __future__ import annotations

import pytest
import respx

from httpx import Response

from pinecone._internal.http import AsyncHTTPClient, HTTPConfig
from pinecone.async_index.records import AsyncRecords


pytestmark = pytest.mark.unit

BASE_URL = "https://my-index-abc123.svc.pinecone.io"


def _make_async_records() -> AsyncRecords:
    return AsyncRecords(
        http=AsyncHTTPClient(api_key="test-key", config=HTTPConfig()),
        base_url=BASE_URL,
    )


class TestAsyncRecordsUpsert:
    """Tests for AsyncRecords.upsert()."""

    @respx.mock
    @pytest.mark.asyncio()
    async def test_upsert_basic(self):
        route = respx.post(f"{BASE_URL}/records/namespaces/test-ns/upsert").mock(
            return_value=Response(201)
        )

        records = _make_async_records()
        result = await records.upsert(
            namespace="test-ns",
            records=[
                {"_id": "rec1", "text": "hello world"},
                {"_id": "rec2", "text": "goodbye"},
            ],
        )

        assert route.called
        assert result is None

    @respx.mock
    @pytest.mark.asyncio()
    async def test_upsert_sends_ndjson_content_type(self):
        route = respx.post(f"{BASE_URL}/records/namespaces/ns1/upsert").mock(
            return_value=Response(201)
        )

        records = _make_async_records()
        await records.upsert(
            namespace="ns1",
            records=[{"_id": "rec1", "text": "hello"}],
        )

        request = route.calls.last.request
        assert request.headers["Content-Type"] == "application/x-ndjson"

    @respx.mock
    @pytest.mark.asyncio()
    async def test_upsert_sends_ndjson_body(self):
        route = respx.post(f"{BASE_URL}/records/namespaces/ns1/upsert").mock(
            return_value=Response(201)
        )

        records = _make_async_records()
        await records.upsert(
            namespace="ns1",
            records=[
                {"_id": "rec1", "text": "hello"},
                {"_id": "rec2", "text": "world"},
            ],
        )

        request = route.calls.last.request
        body = request.content.decode("utf-8")
        lines = body.split("\n")
        assert len(lines) == 2
        assert '"_id"' in lines[0]
        assert '"rec1"' in lines[0]
        assert '"rec2"' in lines[1]

    @respx.mock
    @pytest.mark.asyncio()
    async def test_upsert_sends_api_version_header(self):
        route = respx.post(f"{BASE_URL}/records/namespaces/ns1/upsert").mock(
            return_value=Response(201)
        )

        records = _make_async_records()
        await records.upsert(
            namespace="ns1",
            records=[{"_id": "rec1", "text": "hello"}],
        )

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

    @respx.mock
    @pytest.mark.asyncio()
    async def test_upsert_with_namespace_in_url(self):
        route = respx.post(f"{BASE_URL}/records/namespaces/my-namespace/upsert").mock(
            return_value=Response(201)
        )

        records = _make_async_records()
        await records.upsert(
            namespace="my-namespace",
            records=[{"_id": "rec1", "text": "hello"}],
        )

        assert route.called

    @pytest.mark.asyncio()
    async def test_upsert_empty_namespace_raises(self):
        records = _make_async_records()
        with pytest.raises(ValueError, match="namespace"):
            await records.upsert(namespace="", records=[{"_id": "rec1"}])

    @pytest.mark.asyncio()
    async def test_upsert_empty_records_raises(self):
        records = _make_async_records()
        with pytest.raises(ValueError, match="records"):
            await records.upsert(namespace="ns", records=[])


class TestAsyncRecordsSearch:
    """Tests for AsyncRecords.search()."""

    @respx.mock
    @pytest.mark.asyncio()
    async def test_search_basic(self):
        route = respx.post(f"{BASE_URL}/records/namespaces/test-ns/search").mock(
            return_value=Response(
                200,
                json={
                    "result": {
                        "hits": [
                            {"_id": "rec1", "_score": 0.95, "fields": {"text": "hello"}},
                            {"_id": "rec2", "_score": 0.80, "fields": {}},
                        ]
                    },
                    "usage": {"read_units": 5},
                },
            )
        )

        records = _make_async_records()
        result = await records.search(
            namespace="test-ns",
            top_k=10,
            inputs={"text": "hello"},
        )

        assert route.called
        assert len(result.hits) == 2
        assert result.hits[0].id == "rec1"
        assert result.hits[0].score == 0.95
        assert result.hits[0].fields == {"text": "hello"}
        assert result.hits[1].id == "rec2"
        assert result.hits[1].score == 0.80
        assert result.usage.read_units == 5

    @respx.mock
    @pytest.mark.asyncio()
    async def test_search_with_fields(self):
        route = respx.post(f"{BASE_URL}/records/namespaces/ns1/search").mock(
            return_value=Response(
                200,
                json={
                    "result": {"hits": []},
                    "usage": {"read_units": 1},
                },
            )
        )

        records = _make_async_records()
        await records.search(
            namespace="ns1",
            top_k=5,
            inputs={"text": "test"},
            fields=["text", "category"],
        )

        request = route.calls.last.request
        assert b'"fields"' in request.content
        assert b'"text"' in request.content
        assert b'"category"' in request.content

    @respx.mock
    @pytest.mark.asyncio()
    async def test_search_with_rerank(self):
        route = respx.post(f"{BASE_URL}/records/namespaces/ns1/search").mock(
            return_value=Response(
                200,
                json={
                    "result": {"hits": []},
                    "usage": {"read_units": 1, "rerank_units": 2},
                },
            )
        )

        records = _make_async_records()
        result = await records.search(
            namespace="ns1",
            top_k=100,
            inputs={"text": "test"},
            rerank={"model": "bge-reranker-v2-m3", "rank_fields": ["text"]},
        )

        request = route.calls.last.request
        assert b'"rerank"' in request.content
        assert result.usage.rerank_units == 2

    @respx.mock
    @pytest.mark.asyncio()
    async def test_search_with_vector_query(self):
        route = respx.post(f"{BASE_URL}/records/namespaces/ns1/search").mock(
            return_value=Response(
                200,
                json={
                    "result": {
                        "hits": [
                            {"_id": "rec1", "_score": 0.99},
                        ]
                    },
                    "usage": {"read_units": 1},
                },
            )
        )

        records = _make_async_records()
        result = await records.search(
            namespace="ns1",
            top_k=5,
            vector=[0.1, 0.2, 0.3],
        )

        assert route.called
        assert result.hits[0].id == "rec1"
        assert result.hits[0].score == 0.99

    @respx.mock
    @pytest.mark.asyncio()
    async def test_search_usage_with_embed_tokens(self):
        respx.post(f"{BASE_URL}/records/namespaces/ns1/search").mock(
            return_value=Response(
                200,
                json={
                    "result": {"hits": []},
                    "usage": {
                        "read_units": 3,
                        "embed_total_tokens": 42,
                    },
                },
            )
        )

        records = _make_async_records()
        result = await records.search(
            namespace="ns1",
            top_k=5,
            inputs={"text": "test"},
        )

        assert result.usage.read_units == 3
        assert result.usage.embed_total_tokens == 42
        assert result.usage.rerank_units is None

    @respx.mock
    @pytest.mark.asyncio()
    async def test_search_hit_default_fields(self):
        respx.post(f"{BASE_URL}/records/namespaces/ns1/search").mock(
            return_value=Response(
                200,
                json={
                    "result": {
                        "hits": [
                            {"_id": "rec1", "_score": 0.5},
                        ]
                    },
                    "usage": {"read_units": 1},
                },
            )
        )

        records = _make_async_records()
        result = await records.search(
            namespace="ns1",
            top_k=1,
            inputs={"text": "test"},
        )

        assert result.hits[0].fields == {}

    @respx.mock
    @pytest.mark.asyncio()
    async def test_search_sends_api_version_header(self):
        route = respx.post(f"{BASE_URL}/records/namespaces/ns1/search").mock(
            return_value=Response(
                200,
                json={
                    "result": {"hits": []},
                    "usage": {"read_units": 1},
                },
            )
        )

        records = _make_async_records()
        await records.search(
            namespace="ns1",
            top_k=5,
            inputs={"text": "test"},
        )

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

    @respx.mock
    @pytest.mark.asyncio()
    async def test_search_response_hits_convenience(self):
        respx.post(f"{BASE_URL}/records/namespaces/ns1/search").mock(
            return_value=Response(
                200,
                json={
                    "result": {
                        "hits": [
                            {"_id": "rec1", "_score": 0.9},
                            {"_id": "rec2", "_score": 0.8},
                        ]
                    },
                    "usage": {"read_units": 1},
                },
            )
        )

        records = _make_async_records()
        result = await records.search(
            namespace="ns1",
            top_k=2,
            inputs={"text": "test"},
        )

        # .hits is a convenience property on SearchRecordsResponse
        assert result.hits is result.result.hits
        assert len(result.hits) == 2

    @pytest.mark.asyncio()
    async def test_search_empty_namespace_raises(self):
        records = _make_async_records()
        with pytest.raises(ValueError, match="namespace"):
            await records.search(namespace="", top_k=10)

    @pytest.mark.asyncio()
    async def test_search_top_k_too_low_raises(self):
        records = _make_async_records()
        with pytest.raises(ValueError, match="top_k"):
            await records.search(namespace="ns", top_k=0)


class TestAsyncRecordsModels:
    """Tests for async records model __repr__ methods."""

    @respx.mock
    @pytest.mark.asyncio()
    async def test_search_response_repr(self):
        respx.post(f"{BASE_URL}/records/namespaces/ns1/search").mock(
            return_value=Response(
                200,
                json={
                    "result": {"hits": [{"_id": "rec1", "_score": 0.9}]},
                    "usage": {"read_units": 1},
                },
            )
        )

        records = _make_async_records()
        result = await records.search(
            namespace="ns1",
            top_k=1,
            inputs={"text": "test"},
        )

        assert "hits=1" in repr(result)

    def test_async_records_repr(self):
        records = _make_async_records()
        assert "AsyncRecords(" in repr(records)
        assert BASE_URL in repr(records)
