"""Integration tests for Inference API (async).

These tests require a valid Pinecone API key and make real API calls.

Setup:
    export PINECONE_API_KEY="your-api-key"
    pytest tests/integration/test_async_inference.py -v

Note: Integration tests may incur API costs and are slower than unit tests.
"""

from __future__ import annotations

import asyncio

from typing import TYPE_CHECKING

import pytest

from pinecone.exceptions import APIError


if TYPE_CHECKING:
    from pinecone import AsyncPinecone


pytestmark = [pytest.mark.integration, pytest.mark.asyncio]


class TestAsyncEmbedSuccess:
    """Test successful async embedding generation."""

    async def test_embed_single_text(self, async_integration_client: AsyncPinecone) -> None:
        """Test async embedding generation with a single text.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        response = await async_integration_client.inference.embed(
            model="multilingual-e5-large",
            texts=["Hello world"],
            parameters={"input_type": "passage"},
        )

        # Verify response structure
        assert response is not None
        assert hasattr(response, "embeddings")
        assert hasattr(response, "model")
        assert hasattr(response, "usage")

        # Verify response data
        assert len(response.embeddings) == 1
        assert len(response.embeddings[0]) > 0  # Has dimensions
        assert response.model == "multilingual-e5-large"
        assert response.usage.total_tokens > 0

    async def test_embed_multiple_texts(self, async_integration_client: AsyncPinecone) -> None:
        """Test async embedding generation with multiple texts.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        texts = [
            "Machine learning is a subset of artificial intelligence",
            "Python is a popular programming language",
            "Vector databases store embeddings",
        ]

        response = await async_integration_client.inference.embed(
            model="multilingual-e5-large",
            texts=texts,
            parameters={"input_type": "passage"},
        )

        # Verify correct number of embeddings
        assert len(response.embeddings) == len(texts)

        # Verify all embeddings have same dimension
        first_dim = len(response.embeddings[0])
        assert all(len(emb) == first_dim for emb in response.embeddings)

        # Verify usage tracking
        assert response.usage.total_tokens > 0

    async def test_embed_with_parameters(self, async_integration_client: AsyncPinecone) -> None:
        """Test async embedding generation with model parameters.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        response = await async_integration_client.inference.embed(
            model="multilingual-e5-large",
            texts=["Test text with parameters"],
            parameters={"input_type": "passage", "truncate": "END"},
        )

        assert response is not None
        assert len(response.embeddings) == 1
        assert len(response.embeddings[0]) > 0

    async def test_embed_concurrent_requests(self, async_integration_client: AsyncPinecone) -> None:
        """Test concurrent embedding requests using asyncio.gather.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        text_batches = [
            ["First batch text 1", "First batch text 2"],
            ["Second batch text 1", "Second batch text 2"],
            ["Third batch text 1", "Third batch text 2"],
        ]

        # Run all embed requests concurrently
        tasks = [
            async_integration_client.inference.embed(
                model="multilingual-e5-large",
                texts=batch,
                parameters={"input_type": "passage"},
            )
            for batch in text_batches
        ]
        results = await asyncio.gather(*tasks)

        # Verify all requests succeeded
        assert len(results) == len(text_batches)
        for i, response in enumerate(results):
            assert len(response.embeddings) == len(text_batches[i])
            assert response.usage.total_tokens > 0


class TestAsyncRerankSuccess:
    """Test successful async document reranking."""

    async def test_rerank_basic(self, async_integration_client: AsyncPinecone) -> None:
        """Test basic async document reranking.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        query = "What is machine learning?"
        documents = [
            "Machine learning is a subset of artificial intelligence",
            "Python is a programming language",
            "Deep learning uses neural networks",
        ]

        response = await async_integration_client.inference.rerank(
            model="bge-reranker-v2-m3",
            query=query,
            documents=documents,
        )

        # Verify response structure
        assert response is not None
        assert hasattr(response, "results")
        assert hasattr(response, "model")
        assert hasattr(response, "usage")

        # Verify results
        assert len(response.results) == len(documents)
        assert response.model == "bge-reranker-v2-m3"
        assert response.usage.total_tokens > 0

        # Verify result structure
        for result in response.results:
            assert hasattr(result, "index")
            assert hasattr(result, "score")
            assert 0 <= result.index < len(documents)
            assert isinstance(result.score, float)

    async def test_rerank_with_top_n(self, async_integration_client: AsyncPinecone) -> None:
        """Test async reranking with top_n parameter.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        query = "artificial intelligence"
        documents = [
            "AI is transforming industries",
            "Cooking recipes for beginners",
            "Machine learning applications",
            "Weather forecast for tomorrow",
            "Neural networks and deep learning",
        ]

        response = await async_integration_client.inference.rerank(
            model="bge-reranker-v2-m3",
            query=query,
            documents=documents,
            top_n=3,
        )

        # Should only return top 3 results
        assert len(response.results) == 3

        # Verify results are sorted by score (highest first)
        scores = [result.score for result in response.results]
        assert scores == sorted(scores, reverse=True)

    async def test_rerank_return_documents(self, async_integration_client: AsyncPinecone) -> None:
        """Test async reranking with return_documents flag.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        query = "Python programming"
        documents = [
            "Python is a high-level programming language",
            "JavaScript is used for web development",
            "Machine learning with Python libraries",
        ]

        response = await async_integration_client.inference.rerank(
            model="bge-reranker-v2-m3",
            query=query,
            documents=documents,
            top_n=2,
            return_documents=True,
        )

        # Verify documents are included in results
        for result in response.results:
            assert result.document is not None
            # API returns document as dict with 'text' and 'id' fields
            assert isinstance(result.document, dict)
            assert "text" in result.document
            assert result.document["text"] in documents

    async def test_rerank_without_return_documents(
        self, async_integration_client: AsyncPinecone
    ) -> None:
        """Test async reranking without return_documents flag (default).

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        query = "machine learning"
        documents = ["AI and ML", "Cooking recipes", "Neural networks"]

        response = await async_integration_client.inference.rerank(
            model="bge-reranker-v2-m3",
            query=query,
            documents=documents,
        )

        # Documents should not be included by default
        for result in response.results:
            assert result.document is None

    async def test_rerank_with_parameters(self, async_integration_client: AsyncPinecone) -> None:
        """Test async reranking with model parameters.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        query = "deep learning"
        documents = ["Neural networks", "Cooking", "AI models"]

        response = await async_integration_client.inference.rerank(
            model="bge-reranker-v2-m3",
            query=query,
            documents=documents,
            parameters={},  # Empty dict is valid
        )

        assert response is not None
        assert len(response.results) > 0

    async def test_rerank_concurrent_requests(
        self, async_integration_client: AsyncPinecone
    ) -> None:
        """Test concurrent reranking requests using asyncio.gather.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        queries = [
            "artificial intelligence",
            "machine learning algorithms",
            "neural network architectures",
        ]
        documents = [
            "Deep learning and AI",
            "Cooking recipes",
            "ML model training",
            "Weather patterns",
        ]

        # Run all rerank requests concurrently
        tasks = [
            async_integration_client.inference.rerank(
                model="bge-reranker-v2-m3",
                query=query,
                documents=documents,
                top_n=2,
            )
            for query in queries
        ]
        results = await asyncio.gather(*tasks)

        # Verify all requests succeeded
        assert len(results) == len(queries)
        for response in results:
            assert len(response.results) == 2
            assert response.usage.total_tokens > 0


class TestAsyncErrorHandling:
    """Test async error handling for invalid inputs."""

    async def test_embed_empty_texts(self, async_integration_client: AsyncPinecone) -> None:
        """Test that empty texts list raises ValueError.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        with pytest.raises(ValueError, match="texts must be a non-empty list"):
            await async_integration_client.inference.embed(
                model="multilingual-e5-large",
                texts=[],
            )

    async def test_embed_missing_model(self, async_integration_client: AsyncPinecone) -> None:
        """Test that missing model raises ValueError.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        with pytest.raises(ValueError, match="model must be a non-empty string"):
            await async_integration_client.inference.embed(
                model="",
                texts=["test"],
            )

    async def test_embed_invalid_model(self, async_integration_client: AsyncPinecone) -> None:
        """Test that invalid model name returns API error.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        with pytest.raises(APIError):
            await async_integration_client.inference.embed(
                model="nonexistent-model-12345",
                texts=["test"],
            )

    async def test_rerank_empty_query(self, async_integration_client: AsyncPinecone) -> None:
        """Test that empty query raises ValueError.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        with pytest.raises(ValueError, match="query must be a non-empty string"):
            await async_integration_client.inference.rerank(
                model="bge-reranker-v2-m3",
                query="",
                documents=["doc1"],
            )

    async def test_rerank_empty_documents(self, async_integration_client: AsyncPinecone) -> None:
        """Test that empty documents list raises ValueError.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        with pytest.raises(ValueError, match="documents must be a non-empty list"):
            await async_integration_client.inference.rerank(
                model="bge-reranker-v2-m3",
                query="test query",
                documents=[],
            )

    async def test_rerank_invalid_top_n(self, async_integration_client: AsyncPinecone) -> None:
        """Test that invalid top_n raises ValueError.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        documents = ["doc1", "doc2"]

        # Test with top_n = 0 (invalid)
        with pytest.raises(ValueError, match="top_n must be between"):
            await async_integration_client.inference.rerank(
                model="bge-reranker-v2-m3",
                query="test",
                documents=documents,
                top_n=0,
            )

        # Test with top_n > len(documents) (invalid)
        with pytest.raises(ValueError, match="top_n must be between"):
            await async_integration_client.inference.rerank(
                model="bge-reranker-v2-m3",
                query="test",
                documents=documents,
                top_n=5,
            )

    async def test_rerank_invalid_model(self, async_integration_client: AsyncPinecone) -> None:
        """Test that invalid model name returns API error.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        with pytest.raises(APIError):
            await async_integration_client.inference.rerank(
                model="nonexistent-reranker-12345",
                query="test",
                documents=["doc1"],
            )


class TestAsyncListModelsSuccess:
    """Test successful async list_models operations."""

    async def test_list_models_all(self, async_integration_client: AsyncPinecone) -> None:
        """Test listing all available models asynchronously.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        response = await async_integration_client.inference.list_models()

        # Verify response structure
        assert response is not None
        assert hasattr(response, "models")
        assert len(response.models) > 0

        # Verify model structure
        for model in response.models:
            assert hasattr(model, "model")
            assert hasattr(model, "short_description")
            assert hasattr(model, "type")
            assert model.type in ["embed", "rerank"]
            assert hasattr(model, "modality")
            assert hasattr(model, "max_sequence_length")
            assert hasattr(model, "max_batch_size")
            assert hasattr(model, "provider_name")

            # Embed models should have vector_type
            if model.type == "embed":
                assert hasattr(model, "vector_type")
                assert model.vector_type in ["dense", "sparse"]

    async def test_list_models_filter_by_type_embed(
        self, async_integration_client: AsyncPinecone
    ) -> None:
        """Test listing only embedding models asynchronously.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        response = await async_integration_client.inference.list_models(model_type="embed")

        # Verify all returned models are embedding models
        assert len(response.models) > 0
        for model in response.models:
            assert model.type == "embed"
            assert model.vector_type in ["dense", "sparse"]

    async def test_list_models_filter_by_type_rerank(
        self, async_integration_client: AsyncPinecone
    ) -> None:
        """Test listing only reranking models asynchronously.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        response = await async_integration_client.inference.list_models(model_type="rerank")

        # Verify all returned models are reranking models
        assert len(response.models) > 0
        for model in response.models:
            assert model.type == "rerank"
            assert model.vector_type is None  # rerank models don't have vector_type

    async def test_list_models_filter_by_vector_type_dense(
        self, async_integration_client: AsyncPinecone
    ) -> None:
        """Test listing only dense embedding models asynchronously.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        response = await async_integration_client.inference.list_models(
            model_type="embed", vector_type="dense"
        )

        # Verify all returned models are dense embedding models
        assert len(response.models) > 0
        for model in response.models:
            assert model.type == "embed"
            assert model.vector_type == "dense"

    async def test_list_models_filter_by_vector_type_sparse(
        self, async_integration_client: AsyncPinecone
    ) -> None:
        """Test listing only sparse embedding models asynchronously.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        response = await async_integration_client.inference.list_models(
            model_type="embed", vector_type="sparse"
        )

        # Verify all returned models are sparse embedding models (if any exist)
        for model in response.models:
            assert model.type == "embed"
            assert model.vector_type == "sparse"

    async def test_list_models_response_structure(
        self, async_integration_client: AsyncPinecone
    ) -> None:
        """Test that async list_models response has expected structure for model details.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        response = await async_integration_client.inference.list_models()

        # Find an embedding model to check detailed structure
        embed_models = [m for m in response.models if m.type == "embed"]
        assert len(embed_models) > 0

        model = embed_models[0]

        # Check supported_parameters structure
        if model.supported_parameters:
            for param in model.supported_parameters:
                assert hasattr(param, "parameter")
                assert hasattr(param, "required")
                assert hasattr(param, "type")
                assert hasattr(param, "value_type")

    async def test_list_models_concurrent_requests(
        self, async_integration_client: AsyncPinecone
    ) -> None:
        """Test concurrent list_models requests.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        # Run multiple list_models requests concurrently
        tasks = [
            async_integration_client.inference.list_models(),
            async_integration_client.inference.list_models(model_type="embed"),
            async_integration_client.inference.list_models(model_type="rerank"),
        ]
        results = await asyncio.gather(*tasks)

        # Verify all requests succeeded
        assert len(results) == 3
        for response in results:
            assert response is not None
            assert len(response.models) > 0


class TestAsyncTimeout:
    """Test async timeout parameter handling."""

    async def test_embed_with_timeout(self, async_integration_client: AsyncPinecone) -> None:
        """Test async embedding with custom timeout.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        response = await async_integration_client.inference.embed(
            model="multilingual-e5-large",
            texts=["Test with timeout"],
            parameters={"input_type": "passage"},
            timeout=30.0,
        )

        assert response is not None
        assert len(response.embeddings) == 1

    async def test_rerank_with_timeout(self, async_integration_client: AsyncPinecone) -> None:
        """Test async reranking with custom timeout.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        response = await async_integration_client.inference.rerank(
            model="bge-reranker-v2-m3",
            query="test",
            documents=["doc1", "doc2"],
            timeout=30.0,
        )

        assert response is not None
        assert len(response.results) == 2

    async def test_list_models_with_timeout(self, async_integration_client: AsyncPinecone) -> None:
        """Test listing models with custom timeout asynchronously.

        Args:
            async_integration_client: The async Pinecone client fixture.
        """
        response = await async_integration_client.inference.list_models(timeout=30.0)

        assert response is not None
        assert len(response.models) > 0
