"""Tests for the wrap() universal client wrapper."""

from unittest.mock import MagicMock, patch

from llm_observatory.client import (
    Anthropic,
    AsyncAnthropic,
    _AsyncBaseClient,
    _BaseClient,
    wrap,
)


# ── Sync OpenAI ───────────────────────────────────────────────────


def test_wrap_sync_openai_client():
    """wrap() should return _BaseClient for a sync OpenAI-shaped client."""
    fake = MagicMock()
    fake.chat = MagicMock()
    result = wrap(fake)
    assert isinstance(result, _BaseClient)
    assert result._client is fake


def test_wrap_sync_openai_has_chat_completions():
    """Wrapped sync client should expose chat.completions.create."""
    fake = MagicMock()
    fake.chat = MagicMock()
    result = wrap(fake)
    assert hasattr(result, "chat")
    assert hasattr(result.chat, "completions")
    assert callable(result.chat.completions.create)


def test_wrap_sync_set_metadata():
    """set_metadata() should work on a wrap()-produced client."""
    fake = MagicMock()
    fake.chat = MagicMock()
    result = wrap(fake)
    result.set_metadata(user_id="u1", session_id="s1")
    assert result._metadata == {"user_id": "u1", "session_id": "s1"}


def test_wrap_sync_getattr_proxies():
    """Unknown attributes should proxy to the underlying client."""
    fake = MagicMock()
    fake.chat = MagicMock()
    fake.models = "models-ns"
    result = wrap(fake)
    assert result.models == "models-ns"


@patch("llm_observatory.client.get_transport")
@patch("llm_observatory.client.get_config")
def test_wrap_sync_captures_event(mock_get_config, mock_get_transport):
    """chat.completions.create on a wrapped client should capture an event."""
    mock_get_config.return_value = {"enabled": True, "redact_messages": False}

    mock_response = MagicMock()
    mock_response.id = "chatcmpl-abc123"
    mock_response.model = "gpt-4o"
    mock_response.choices = [
        MagicMock(
            index=0,
            finish_reason="stop",
            message=MagicMock(role="assistant", content="Hi!", tool_calls=None),
        )
    ]
    mock_response.usage = MagicMock(
        prompt_tokens=10, completion_tokens=5, total_tokens=15
    )

    fake = MagicMock()
    fake.chat.completions.create.return_value = mock_response

    mock_transport = MagicMock()
    mock_get_transport.return_value = mock_transport

    client = wrap(fake)
    resp = client.chat.completions.create(
        model="gpt-4o", messages=[{"role": "user", "content": "hi"}]
    )
    assert resp is mock_response
    mock_transport.enqueue.assert_called_once()


# ── Async OpenAI ──────────────────────────────────────────────────


def test_wrap_async_openai_client():
    """wrap() should return _AsyncBaseClient for an async OpenAI-shaped client."""

    class FakeAsyncOpenAI:
        def __init__(self):
            self.chat = MagicMock()

    fake = FakeAsyncOpenAI()
    result = wrap(fake)
    assert isinstance(result, _AsyncBaseClient)
    assert result._client is fake


# ── Already-wrapped (no double-wrap) ─────────────────────────────


@patch("llm_observatory.client.openai.OpenAI")
def test_wrap_already_wrapped_baseclient(mock_openai_cls):
    """wrap() should return the same instance if already a _BaseClient."""
    mock_openai_cls.return_value = MagicMock()
    from llm_observatory.client import OpenAI as C0OpenAI

    client = C0OpenAI(api_key="sk-test")
    assert wrap(client) is client


@patch("llm_observatory.client.openai.AsyncOpenAI")
def test_wrap_already_wrapped_async(mock_async_cls):
    """wrap() should return the same instance if already _AsyncBaseClient."""
    mock_async_cls.return_value = MagicMock()
    from llm_observatory.client import AsyncOpenAI as C0Async

    client = C0Async(api_key="sk-test")
    assert wrap(client) is client


# ── Anthropic ─────────────────────────────────────────────────────


def test_wrap_sync_anthropic_client():
    """wrap() should detect and wrap a sync Anthropic-shaped client."""
    fake = MagicMock(spec=["messages", "count_tokens", "models"])
    result = wrap(fake)
    assert isinstance(result, Anthropic)
    assert result._client is fake
    assert hasattr(result, "messages")


def test_wrap_async_anthropic_client():
    """wrap() should detect and wrap an async Anthropic-shaped client."""

    class FakeAsyncAnthropic:
        def __init__(self):
            self.messages = MagicMock()

    fake = FakeAsyncAnthropic()
    result = wrap(fake)
    assert isinstance(result, AsyncAnthropic)
    assert result._client is fake


def test_wrap_anthropic_set_metadata():
    """set_metadata() should work on a wrap()-produced Anthropic client."""
    fake = MagicMock(spec=["messages", "count_tokens"])
    result = wrap(fake)
    result.set_metadata(user_id="u2", tags=["test"])
    assert result._metadata == {"user_id": "u2", "tags": ["test"]}


# ── Unsupported ───────────────────────────────────────────────────


def test_wrap_unsupported_raises():
    """wrap() should raise TypeError for unknown client types."""
    import pytest

    with pytest.raises(TypeError, match="does not support"):
        wrap("not-a-client")


# ── Langfuse-wrapped OpenAI ───────────────────────────────────────


def test_wrap_langfuse_openai_client():
    """wrap() should handle a Langfuse-wrapped OpenAI client (has .chat)."""
    # Langfuse's OpenAI is OpenAI-shaped — it has .chat
    fake = MagicMock()
    fake.chat = MagicMock()
    fake.__class__ = type("OpenAI", (), {})  # Langfuse's class name
    result = wrap(fake)
    assert isinstance(result, _BaseClient)
    assert result._client is fake
