"""Tests for AsyncAnthropic wrapper."""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from llm_observatory.client import AsyncAnthropic


def _make_async_anthropic_client():
    """Helper: create AsyncAnthropic wrapper with mocked underlying client."""
    with patch.dict("sys.modules", {"anthropic": MagicMock()}):
        import sys

        mock_mod = sys.modules["anthropic"]
        mock_instance = MagicMock()
        mock_mod.AsyncAnthropic.return_value = mock_instance
        client = AsyncAnthropic(api_key="sk-ant-test")
        return client, mock_instance


def test_async_anthropic_has_messages():
    """AsyncAnthropic should expose messages.create."""
    client, _ = _make_async_anthropic_client()
    assert hasattr(client, "messages")


def test_async_anthropic_set_metadata():
    """set_metadata should persist metadata."""
    client, _ = _make_async_anthropic_client()
    client.set_metadata(user_id="u1", tags=["test"])
    assert client._metadata == {"user_id": "u1", "tags": ["test"]}


def test_async_anthropic_getattr_proxies():
    """Unknown attributes should proxy to the underlying client."""
    client, mock_instance = _make_async_anthropic_client()
    mock_instance.count_tokens = "counter"
    assert client.count_tokens == "counter"


@pytest.mark.asyncio
@patch("llm_observatory.client.get_transport")
@patch("llm_observatory.client.get_config")
async def test_async_anthropic_create_captures_event(mock_get_config, mock_get_transport):
    """messages.create should capture an event via transport."""
    mock_get_config.return_value = {"enabled": True, "redact_messages": False}

    mock_text_block = MagicMock()
    mock_text_block.type = "text"
    mock_text_block.text = "Hello from async Claude!"

    mock_response = MagicMock()
    mock_response.id = "msg_async"
    mock_response.model = "claude-sonnet-4-20250514"
    mock_response.content = [mock_text_block]
    mock_response.stop_reason = "end_turn"
    mock_response.usage = MagicMock(input_tokens=20, output_tokens=8)

    client, mock_instance = _make_async_anthropic_client()
    mock_instance.messages.create = AsyncMock(return_value=mock_response)

    mock_transport = MagicMock()
    mock_get_transport.return_value = mock_transport

    result = await client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hi"}],
    )

    assert result is mock_response
    mock_transport.enqueue.assert_called_once()

    event = mock_transport.enqueue.call_args[0][0]
    assert event["request"]["model"] == "claude-sonnet-4-20250514"
    assert event["response"]["id"] == "msg_async"
    assert event["usage"]["prompt_tokens"] == 20
    assert event["usage"]["completion_tokens"] == 8


@pytest.mark.asyncio
@patch("llm_observatory.client.get_config")
async def test_async_anthropic_disabled_passthrough(mock_get_config):
    """When disabled, create should pass through."""
    mock_get_config.return_value = {"enabled": False}

    mock_response = MagicMock()
    client, mock_instance = _make_async_anthropic_client()
    mock_instance.messages.create = AsyncMock(return_value=mock_response)

    result = await client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hi"}],
    )

    assert result is mock_response


@pytest.mark.asyncio
@patch("llm_observatory.client.get_transport")
@patch("llm_observatory.client.get_config")
async def test_async_anthropic_error_captures_event(mock_get_config, mock_get_transport):
    """On error, the event should be captured with error info."""
    mock_get_config.return_value = {"enabled": True, "redact_messages": False}

    client, mock_instance = _make_async_anthropic_client()
    mock_instance.messages.create = AsyncMock(side_effect=Exception("async rate limit"))

    mock_transport = MagicMock()
    mock_get_transport.return_value = mock_transport

    with pytest.raises(Exception, match="async rate limit"):
        await client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1024,
            messages=[{"role": "user", "content": "Hi"}],
        )

    mock_transport.enqueue.assert_called_once()
    event = mock_transport.enqueue.call_args[0][0]
    assert event["error"]["type"] == "Exception"
    assert "async rate limit" in event["error"]["message"]


@pytest.mark.asyncio
@patch("llm_observatory.client.get_transport")
@patch("llm_observatory.client.get_config")
async def test_async_anthropic_per_request_metadata(mock_get_config, mock_get_transport):
    """Per-request metadata should override client-level metadata."""
    mock_get_config.return_value = {"enabled": True, "redact_messages": False}

    mock_text_block = MagicMock()
    mock_text_block.type = "text"
    mock_text_block.text = "Hi"

    mock_response = MagicMock()
    mock_response.id = "msg_meta"
    mock_response.model = "claude-sonnet-4-20250514"
    mock_response.content = [mock_text_block]
    mock_response.stop_reason = "end_turn"
    mock_response.usage = MagicMock(input_tokens=10, output_tokens=5)

    client, mock_instance = _make_async_anthropic_client()
    mock_instance.messages.create = AsyncMock(return_value=mock_response)

    mock_transport = MagicMock()
    mock_get_transport.return_value = mock_transport

    client.set_metadata(user_id="global-user", session_id="s1")
    await client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hi"}],
        metadata={"user_id": "request-user"},
    )

    event = mock_transport.enqueue.call_args[0][0]
    assert event["metadata"]["user_id"] == "request-user"
    assert event["metadata"]["session_id"] == "s1"


@pytest.mark.asyncio
@patch("llm_observatory.client.get_transport")
@patch("llm_observatory.client.get_config")
async def test_async_anthropic_streaming_returns_wrapper(mock_get_config, mock_get_transport):
    """When stream=True, should return AnthropicStreamWrapper."""
    mock_get_config.return_value = {"enabled": True, "redact_messages": False}

    from llm_observatory.streaming import AnthropicStreamWrapper

    mock_stream = MagicMock()
    client, mock_instance = _make_async_anthropic_client()
    mock_instance.messages.create = AsyncMock(return_value=mock_stream)

    mock_transport = MagicMock()
    mock_get_transport.return_value = mock_transport

    result = await client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hi"}],
        stream=True,
    )

    assert isinstance(result, AnthropicStreamWrapper)


@pytest.mark.asyncio
@patch("llm_observatory.client.get_transport")
@patch("llm_observatory.client.get_config")
async def test_async_anthropic_error_sanitizes_secrets(mock_get_config, mock_get_transport):
    """Error messages containing secrets should be sanitized."""
    mock_get_config.return_value = {"enabled": True, "redact_messages": False}

    client, mock_instance = _make_async_anthropic_client()
    mock_instance.messages.create = AsyncMock(
        side_effect=Exception("Failed with Bearer sk-ant-api03-verylongkey123456 auth")
    )

    mock_transport = MagicMock()
    mock_get_transport.return_value = mock_transport

    with pytest.raises(Exception):
        await client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1024,
            messages=[{"role": "user", "content": "Hi"}],
        )

    event = mock_transport.enqueue.call_args[0][0]
    assert "sk-ant-api03-verylongkey123456" not in event["error"]["message"]
