"""Tests for inference response models - focus on caching behavior."""

import pytest

from pinecone.__interface__.inference import (
    EmbeddingResponseProtocol,
    RerankResponseProtocol,
)
from pinecone.models.inference import (
    Embedding,
    EmbedResponse,
    RerankResponse,
    RerankResult,
    Usage,
)


pytestmark = pytest.mark.unit


class TestEmbedResponse:
    """Tests for EmbedResponse model - focus on caching behavior."""

    def test_embeddings_property_returns_correct_data(self) -> None:
        """Embeddings property should return list of embedding vectors."""
        data = [
            Embedding(values=[1.0, 2.0, 3.0], index=0),
            Embedding(values=[4.0, 5.0, 6.0], index=1),
        ]
        response = EmbedResponse(
            data=data, model="test-model", vector_type="dense", usage=Usage(total_tokens=10)
        )

        embeddings = response.embeddings
        assert len(embeddings) == 2
        assert embeddings[0] == [1.0, 2.0, 3.0]
        assert embeddings[1] == [4.0, 5.0, 6.0]

    def test_embeddings_property_is_cached(self) -> None:
        """Embeddings property should return the same list instance on multiple accesses."""
        data = [
            Embedding(values=[1.0, 2.0, 3.0], index=0),
            Embedding(values=[4.0, 5.0, 6.0], index=1),
        ]
        response = EmbedResponse(
            data=data, model="test-model", vector_type="dense", usage=Usage(total_tokens=10)
        )

        # Access embeddings multiple times
        embeddings1 = response.embeddings
        embeddings2 = response.embeddings
        embeddings3 = response.embeddings

        # Should be the exact same object (cached)
        assert embeddings1 is embeddings2
        assert embeddings2 is embeddings3

    def test_model_property_returns_model_name(self) -> None:
        """Model property should return the model name."""
        data = [Embedding(values=[1.0], index=0)]
        response = EmbedResponse(
            data=data,
            model="multilingual-e5-large",
            vector_type="dense",
            usage=Usage(total_tokens=1),
        )

        assert response.model == "multilingual-e5-large"

    def test_implements_protocol(self) -> None:
        """EmbedResponse should implement EmbeddingResponseProtocol."""
        data = [Embedding(values=[1.0], index=0)]
        response = EmbedResponse(
            data=data, model="test-model", vector_type="dense", usage=Usage(total_tokens=1)
        )

        assert isinstance(response, EmbeddingResponseProtocol)

    def test_empty_embeddings(self) -> None:
        """Should handle empty embedding list."""
        response = EmbedResponse(
            data=[], model="test-model", vector_type="dense", usage=Usage(total_tokens=0)
        )

        assert response.embeddings == []
        assert response.model == "test-model"

    def test_multiple_iterations_use_cached_list(self) -> None:
        """Iterating over embeddings multiple times should use cached list."""
        data = [Embedding(values=[float(i)] * 3, index=i) for i in range(100)]
        response = EmbedResponse(
            data=data, model="test-model", vector_type="dense", usage=Usage(total_tokens=100)
        )

        # First iteration - creates the cached list
        count1 = sum(1 for _ in response.embeddings)

        # Second iteration - uses cached list (no new allocation)
        count2 = sum(1 for _ in response.embeddings)

        # Access the same object both times
        assert count1 == count2 == 100
        assert response.embeddings is response.embeddings


class TestRerankResponse:
    """Tests for RerankResponse model - focus on caching behavior."""

    def test_results_property_returns_correct_data(self) -> None:
        """Results property should return list of RerankResult objects."""
        data = [
            RerankResult(index=0, score=0.95, document="doc1"),
            RerankResult(index=1, score=0.87, document="doc2"),
        ]
        response = RerankResponse(data=data, model="test-model", usage=Usage(total_tokens=10))

        results = response.results
        assert len(results) == 2
        assert results[0].index == 0
        assert results[0].score == 0.95
        assert results[0].document == "doc1"
        assert results[1].index == 1
        assert results[1].score == 0.87
        assert results[1].document == "doc2"

    def test_results_property_is_cached(self) -> None:
        """Results property should return the same list instance on multiple accesses."""
        data = [
            RerankResult(index=0, score=0.95),
            RerankResult(index=1, score=0.87),
        ]
        response = RerankResponse(data=data, model="test-model", usage=Usage(total_tokens=10))

        # Access results multiple times
        results1 = response.results
        results2 = response.results
        results3 = response.results

        # Should be the exact same object (cached)
        assert results1 is results2
        assert results2 is results3

    def test_model_property_returns_model_name(self) -> None:
        """Model property should return the model name."""
        data = [RerankResult(index=0, score=0.95)]
        response = RerankResponse(
            data=data, model="bge-reranker-v2-m3", usage=Usage(total_tokens=1)
        )

        assert response.model == "bge-reranker-v2-m3"

    def test_implements_protocol(self) -> None:
        """RerankResponse should implement RerankResponseProtocol."""
        data = [RerankResult(index=0, score=0.95)]
        response = RerankResponse(data=data, model="test-model", usage=Usage(total_tokens=1))

        assert isinstance(response, RerankResponseProtocol)

    def test_empty_results(self) -> None:
        """Should handle empty results list."""
        response = RerankResponse(data=[], model="test-model", usage=Usage(total_tokens=0))

        assert response.results == []
        assert response.model == "test-model"

    def test_rerank_result_without_document(self) -> None:
        """RerankResult should work without document field."""
        result = RerankResult(index=5, score=0.73)
        # msgspec Struct - check the actual fields
        assert result.index == 5
        assert result.score == 0.73
        assert result.document is None

    def test_rerank_result_with_document(self) -> None:
        """RerankResult should include document when provided."""
        result = RerankResult(index=5, score=0.73, document="test document")
        assert result.index == 5
        assert result.score == 0.73
        assert result.document == "test document"

    def test_multiple_iterations_use_cached_list(self) -> None:
        """Iterating over results multiple times should use cached list."""
        data = [RerankResult(index=i, score=1.0 - i * 0.01) for i in range(100)]
        response = RerankResponse(data=data, model="test-model", usage=Usage(total_tokens=100))

        # First iteration - creates the cached list
        count1 = sum(1 for _ in response.results)

        # Second iteration - uses cached list (no new allocation)
        count2 = sum(1 for _ in response.results)

        # Access the same object both times
        assert count1 == count2 == 100
        assert response.results is response.results


class TestCachingPerformance:
    """Tests specifically for caching behavior to avoid repeated allocations."""

    def test_embed_response_avoids_repeated_list_allocation(self) -> None:
        """Multiple accesses to embeddings should not create new lists."""
        data = [Embedding(values=[float(i)] * 768, index=i) for i in range(10)]
        response = EmbedResponse(
            data=data, model="test-model", vector_type="dense", usage=Usage(total_tokens=10)
        )

        # Get the first embeddings list
        first = response.embeddings
        first_id = id(first)

        # Access multiple times - should be the same object
        for _ in range(100):
            current = response.embeddings
            assert id(current) == first_id, "New list allocated on repeated access!"

    def test_rerank_response_avoids_repeated_list_allocation(self) -> None:
        """Multiple accesses to results should not create new lists."""
        data = [RerankResult(index=i, score=0.9 - i * 0.1) for i in range(10)]
        response = RerankResponse(data=data, model="test-model", usage=Usage(total_tokens=10))

        # Get the first results list
        first = response.results
        first_id = id(first)

        # Access multiple times - should be the same object
        for _ in range(100):
            current = response.results
            assert id(current) == first_id, "New list allocated on repeated access!"

    def test_loop_access_pattern(self) -> None:
        """Test common pattern of accessing properties in a loop."""
        # Common user pattern that would cause repeated allocations without caching
        embed_data = [Embedding(values=[1.0, 2.0], index=i) for i in range(5)]
        embed_response = EmbedResponse(
            data=embed_data, model="test", vector_type="dense", usage=Usage(total_tokens=5)
        )

        # Simulate user code that accesses embeddings in a loop
        results = []
        for _ in range(10):
            for embedding in embed_response.embeddings:
                results.append(sum(embedding))

        # Verify we get correct results
        assert len(results) == 50  # 10 iterations * 5 embeddings

        # Verify caching worked
        assert embed_response.embeddings is embed_response.embeddings
