"""Pytest configuration and fixtures.

This module sets up test fixtures and configuration for the test suite.
It automatically loads environment variables from .env files for local testing.
"""

from __future__ import annotations

import os

from pathlib import Path
from typing import TYPE_CHECKING

import pytest
import pytest_asyncio
import respx

from pinecone import AsyncPinecone, Pinecone


if TYPE_CHECKING:
    from collections.abc import AsyncGenerator, Generator

# Import dotenv only if available (it's an optional test dependency)
try:
    from dotenv import load_dotenv
except ImportError:
    load_dotenv = None  # type: ignore[assignment,misc]


def pytest_configure(config: pytest.Config) -> None:
    """Configure pytest and load environment variables from .env files.

    This function runs once before tests start. It loads environment variables
    from .env files if they exist, allowing developers to store API keys and
    other secrets locally without committing them to version control.

    The function searches for .env files in the following locations (in order):
    1. tests/.env - Test-specific environment variables
    2. .env - Project root environment variables

    Args:
        config: Pytest configuration object.
    """
    # Register custom markers
    config.addinivalue_line(
        "markers",
        "unit: Fast unit tests that don't require external services",
    )
    config.addinivalue_line(
        "markers",
        "integration: Tests requiring real Pinecone API connection",
    )
    config.addinivalue_line(
        "markers",
        "slow: Tests that take longer to run",
    )

    if load_dotenv is None:
        # dotenv not installed - skip loading .env files
        return

    # Get the repository root (parent of tests directory)
    tests_dir = Path(__file__).parent
    repo_root = tests_dir.parent

    # Load .env files in order of precedence (later files override earlier ones)
    env_files = [
        repo_root / ".env",  # Project root .env
        tests_dir / ".env",  # Test-specific .env (higher precedence)
    ]

    for env_file in env_files:
        if env_file.exists():
            load_dotenv(env_file, override=True)
            # Only print in verbose mode to avoid cluttering test output
            if config.option.verbose:
                print(f"Loaded environment variables from {env_file}")


@pytest.fixture()
def api_key() -> str:
    """Get Pinecone API key from environment.

    Returns:
        Pinecone API key.

    Raises:
        pytest.skip: If PINECONE_API_KEY is not set.
    """
    key = os.getenv("PINECONE_API_KEY")
    if not key:
        pytest.skip("PINECONE_API_KEY environment variable not set")
    return key


@pytest.fixture()
def index_host() -> str:
    """Get Pinecone index host from environment.

    Returns:
        Pinecone index host URL.

    Raises:
        pytest.skip: If PINECONE_INDEX_HOST is not set.
    """
    host = os.getenv("PINECONE_INDEX_HOST")
    if not host:
        pytest.skip("PINECONE_INDEX_HOST environment variable not set")
    return host


@pytest.fixture()
def test_index_name() -> str:
    """Get test index name from environment.

    Returns:
        Test index name (defaults to "test-index" if not set).
    """
    return os.getenv("PINECONE_TEST_INDEX_NAME", "test-index")


# ---------------------------------------------------------------------------
# Mock HTTP fixtures
# ---------------------------------------------------------------------------


@pytest.fixture()
def mock_http_client() -> Generator[respx.MockRouter, None, None]:
    """Mock HTTP client for unit tests.

    Provides a respx MockRouter for mocking HTTP requests. The router is
    automatically started and stopped around each test.

    Yields:
        respx.MockRouter: A mock router for registering HTTP responses.

    Example:
        def test_api_call(mock_http_client):
            mock_http_client.get("https://api.pinecone.io/test").mock(
                return_value=httpx.Response(200, json={"ok": True})
            )
            # Make requests...
    """
    with respx.mock as router:
        yield router


# ---------------------------------------------------------------------------
# Sample data fixtures
# ---------------------------------------------------------------------------


@pytest.fixture()
def sample_vectors() -> list[dict[str, object]]:
    """Sample vector data for tests.

    Returns:
        List of 10 sample vectors with 1536 dimensions.
    """
    return [{"id": f"vec{i}", "values": [0.1] * 1536} for i in range(10)]


@pytest.fixture()
def sample_vectors_small() -> list[dict[str, object]]:
    """Sample vector data with small dimensions for faster tests.

    Returns:
        List of 10 sample vectors with 8 dimensions.
    """
    return [{"id": f"vec{i}", "values": [0.1 * (i + 1)] * 8} for i in range(10)]


# ---------------------------------------------------------------------------
# Integration test fixtures
# ---------------------------------------------------------------------------


@pytest.fixture()
def integration_client() -> Generator[Pinecone, None, None]:
    """Real Pinecone client for integration tests.

    This fixture creates a Pinecone client configured with real credentials
    from environment variables. Tests using this fixture will skip if
    PINECONE_API_KEY is not set.

    The fixture properly closes the client after each test to release HTTP
    connection resources and avoid resource warnings.

    Yields:
        Pinecone: A configured Pinecone client.

    Raises:
        pytest.skip: If PINECONE_API_KEY is not set.
    """
    import gc

    api_key = os.getenv("PINECONE_API_KEY")
    if not api_key:
        pytest.skip("No API key for integration tests")

    client = Pinecone(api_key=api_key)
    try:
        yield client
    finally:
        # Ensure the client is closed even if the test fails
        # This releases HTTP connections and prevents SSL socket warnings
        client.close()

        # Give httpx a moment to finish closing connections before garbage collection
        # httpx.Client.close() is synchronous but connection cleanup can be deferred
        import time

        time.sleep(0.1)  # 100ms delay to allow connection cleanup

        # Force garbage collection to clean up any lingering references
        # This helps prevent SSL socket resource warnings in CI environments
        # where tests run in quick succession
        gc.collect()


@pytest_asyncio.fixture()
async def async_integration_client() -> AsyncGenerator[AsyncPinecone, None]:
    """Async Pinecone client for integration tests.

    This fixture creates an async Pinecone client configured with real
    credentials from environment variables. Tests using this fixture will
    skip if PINECONE_API_KEY is not set.

    The fixture properly closes the client after each test to release HTTP
    connection resources and avoid event loop issues.

    Yields:
        AsyncPinecone: A configured async Pinecone client.

    Raises:
        pytest.skip: If PINECONE_API_KEY is not set.
    """
    import gc

    api_key = os.getenv("PINECONE_API_KEY")
    if not api_key:
        pytest.skip("No API key for integration tests")

    client = AsyncPinecone(api_key=api_key)
    try:
        yield client
    finally:
        # Ensure the client is closed even if the test fails
        await client.close()

        # Give httpx a moment to finish closing connections before garbage collection
        # httpx.AsyncClient.aclose() completes but connection cleanup can be deferred
        import asyncio

        await asyncio.sleep(0.1)  # 100ms delay to allow connection cleanup

        # Force garbage collection to clean up any lingering references
        # This helps prevent SSL socket resource warnings in CI environments
        gc.collect()
