"""Comprehensive tests for sync MiruvorClient."""
from unittest.mock import patch

import pytest

from miruvor import (
    AuthenticationError,
    MiruvorClient,
    RateLimitError,
    ServerError,
    ValidationError,
)

from .conftest import create_mock_response


@pytest.fixture
def client():
    """Create test client."""
    return MiruvorClient(
        base_url="https://api.example.com",
        api_key="test-key",
        token="test-token",
    )


class TestStoreMethod:
    """Tests for store() method."""

    def test_store_success(self, client, mock_store_response):
        """Test successful store operation."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(200, mock_store_response)

            response = client.store(text="Test memory")

            assert response.memory_id == "mem_123abc"
            assert response.storage_time_ms == 15.5
            assert response.synapses_created == 42
            assert response.status == "success"

    def test_store_with_tags_and_metadata(self, client, mock_store_response):
        """Test store with tags and metadata."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(200, mock_store_response)

            response = client.store(
                text="Test memory",
                tags=["important", "user"],
                metadata={"source": "test"},
            )

            assert response.memory_id == "mem_123abc"
            call_args = mock_req.call_args
            payload = call_args.kwargs["json"]
            assert payload["tags"] == ["important", "user"]
            assert payload["metadata"] == {"source": "test"}

    def test_store_empty_text_raises(self, client):
        """Test that empty text raises ValidationError."""
        with pytest.raises(ValueError, match="Text cannot be empty"):
            client.store(text="")

    def test_store_whitespace_text_raises(self, client):
        """Test that whitespace-only text raises ValidationError."""
        with pytest.raises(ValueError, match="Text cannot be empty"):
            client.store(text="   ")


class TestRetrieveMethod:
    """Tests for retrieve() method."""

    def test_retrieve_success(self, client, mock_retrieve_response):
        """Test successful retrieve operation."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(
                200, mock_retrieve_response
            )

            response = client.retrieve(query="test query")

            assert response.num_results == 2
            assert response.results[0].score == 0.95
            assert response.results[0].data["text"] == "First result"

    def test_retrieve_with_top_k(self, client, mock_retrieve_response):
        """Test retrieve with custom top_k."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(
                200, mock_retrieve_response
            )

            response = client.retrieve(query="test", top_k=10)

            call_args = mock_req.call_args
            assert call_args.kwargs["params"]["top_k"] == 10

    def test_retrieve_with_sparse(self, client, mock_retrieve_response):
        """Test retrieve with sparse override."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(
                200, mock_retrieve_response
            )

            response = client.retrieve(query="test", use_sparse=True)

            call_args = mock_req.call_args
            assert call_args.kwargs["params"]["use_sparse"] is True


class TestIngestMethod:
    """Tests for ingest() method."""

    def test_ingest_success(self, client, mock_ingest_response):
        """Test successful ingest operation."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(
                200, mock_ingest_response
            )

            response = client.ingest(content="Long document content")

            assert response.success is True
            assert response.message_id == "msg_456def"
            assert response.status == "queued"

    def test_ingest_with_priority(self, client, mock_ingest_response):
        """Test ingest with custom priority."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(
                200, mock_ingest_response
            )

            response = client.ingest(content="Urgent content", priority="urgent")

            call_args = mock_req.call_args
            payload = call_args.kwargs["json"]
            assert payload["priority"] == "urgent"


class TestHealthMethod:
    """Tests for health() method."""

    def test_health_success(self, client, mock_health_response):
        """Test health check."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(
                200, mock_health_response
            )

            response = client.health()

            assert response["status"] == "healthy"
            assert response["database"] == "connected"


class TestBatchOperations:
    """Tests for batch operations."""

    def test_store_batch(self, client, mock_store_response):
        """Test batch store operation."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(
                200, mock_store_response
            )

            memories = [
                {"text": "Memory 1", "tags": ["tag1"]},
                {"text": "Memory 2", "metadata": {"key": "value"}},
                {"text": "Memory 3"},
            ]

            responses = client.store_batch(memories)

            assert len(responses) == 3
            assert all(r.memory_id == "mem_123abc" for r in responses)
            assert mock_req.call_count == 3


class TestErrorHandling:
    """Tests for error handling."""

    def test_authentication_error(self, client):
        """Test 401 raises AuthenticationError."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(
                401,
                {"detail": "Invalid API key"},
            )

            with pytest.raises(AuthenticationError) as exc_info:
                client.store(text="Test")

            assert "Invalid API key" in str(exc_info.value)
            assert exc_info.value.status_code == 401

    def test_rate_limit_error_with_retry_after(self, client):
        """Test 429 raises RateLimitError with retry_after."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(
                429,
                {"detail": "Rate limit exceeded"},
                headers={"Retry-After": "120"},
            )

            with pytest.raises(RateLimitError) as exc_info:
                client.store(text="Test")

            assert exc_info.value.retry_after == 120

    def test_server_error(self, client):
        """Test 500 raises ServerError."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(
                500,
                {"detail": "Internal server error"},
            )

            with pytest.raises(ServerError) as exc_info:
                client.store(text="Test")

            assert exc_info.value.status_code == 500

    def test_validation_error(self, client):
        """Test 422 raises ValidationError."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.return_value = create_mock_response(
                422,
                {"detail": "Validation failed"},
            )

            with pytest.raises(ValidationError):
                client.store(text="Test")


class TestRetryLogic:
    """Tests for retry behavior."""

    def test_retries_on_server_error(self, client, mock_store_response):
        """Test that requests are retried on 500 errors."""
        with patch.object(client.session, "request") as mock_req:
            mock_req.side_effect = [
                create_mock_response(500, {"detail": "Server error"}),
                create_mock_response(500, {"detail": "Server error"}),
                create_mock_response(200, mock_store_response),
            ]

            response = client.store(text="Test")

            assert response.memory_id == "mem_123abc"
            assert mock_req.call_count == 3


class TestContextManager:
    """Tests for context manager support."""

    def test_context_manager_closes_session(self):
        """Test that context manager closes session."""
        with MiruvorClient(
            base_url="https://api.example.com",
            api_key="test-key",
        ) as client:
            assert client.session is not None

    def test_context_manager_with_operations(self, mock_store_response):
        """Test operations within context manager."""
        with MiruvorClient(
            base_url="https://api.example.com",
            api_key="test-key",
        ) as client:
            with patch.object(client.session, "request") as mock_req:
                mock_req.return_value = create_mock_response(
                    200, mock_store_response
                )
                response = client.store(text="Test")
                assert response.memory_id == "mem_123abc"
