"""Tests for Anthropic SDK patching and record generation."""

from types import SimpleNamespace
from unittest.mock import MagicMock, patch

import anthropic

from caliper._anthropic import (
    _build_record_from_error,
    _build_record_from_response,
    _classify_error,
    _extract_tool_calls,
    _extract_tools_provided,
    _process_event,
)
from caliper._features import features
from caliper._patch_helpers import set_capture_tools

# ---------------------------------------------------------------------------
# Fake response/event objects
# ---------------------------------------------------------------------------


def _make_usage(input_tokens=100, output_tokens=50, cache_read=0):
    return SimpleNamespace(
        input_tokens=input_tokens,
        output_tokens=output_tokens,
        cache_read_input_tokens=cache_read,
    )


def _make_response(model="claude-sonnet-4-20250514", input_tokens=100, output_tokens=50, cache_read=0):
    return SimpleNamespace(
        id="msg_abc",
        model=model,
        usage=_make_usage(input_tokens, output_tokens, cache_read),
        stop_reason="end_turn",
        _request_id="req_xyz123",
    )


def _make_event(event_type, **kwargs):
    ns = SimpleNamespace(type=event_type, **kwargs)
    return ns


# ---------------------------------------------------------------------------
# _classify_error
# ---------------------------------------------------------------------------


def test_classify_rate_limit():
    exc = anthropic.RateLimitError(
        message="rate limited",
        response=MagicMock(status_code=429),
        body=None,
    )
    status, http_status, error_code = _classify_error(exc)
    assert status == "rate_limited"
    assert http_status == 429


def test_classify_timeout():
    exc = anthropic.APITimeoutError(request=MagicMock())
    status, http_status, _ = _classify_error(exc)
    assert status == "timeout"
    assert http_status is None


def test_classify_connection_error():
    exc = anthropic.APIConnectionError(request=MagicMock())
    status, http_status, _ = _classify_error(exc)
    assert status == "timeout"
    assert http_status is None


def test_classify_api_status_error():
    exc = anthropic.APIStatusError(
        message="server error",
        response=MagicMock(status_code=500),
        body=None,
    )
    status, http_status, _ = _classify_error(exc)
    assert status == "error"
    assert http_status == 500


def test_classify_generic_error():
    status, http_status, _ = _classify_error(RuntimeError("boom"))
    assert status == "error"
    assert http_status is None


# ---------------------------------------------------------------------------
# _build_record_from_response
# ---------------------------------------------------------------------------


def test_build_record_from_response():
    response = _make_response(input_tokens=100, output_tokens=50)
    record = _build_record_from_response(
        response, {"model": "test"}, duration_ms=150, request_meta={}, request_id="test-uuid"
    )

    assert record.provider == "anthropic"
    assert record.model == "claude-sonnet-4-20250514"
    assert record.endpoint == "chat"
    assert record.tokens_input == 100
    assert record.tokens_output == 50
    assert record.tokens_total == 150
    assert record.status == "success"
    assert record.http_status == 200
    assert record.provider_request_id == "req_xyz123"
    assert record.cached is False


def test_build_record_from_response_cached():
    response = _make_response(cache_read=50)
    record = _build_record_from_response(response, {}, duration_ms=100, request_meta={}, request_id="test-uuid")
    assert record.cached is True


# ---------------------------------------------------------------------------
# _build_record_from_error
# ---------------------------------------------------------------------------


def test_build_record_from_error():
    exc = anthropic.RateLimitError(
        message="rate limited",
        response=MagicMock(status_code=429),
        body=None,
    )
    record = _build_record_from_error(
        exc, {"model": "test-model"}, duration_ms=50, request_meta={}, request_id="test-uuid"
    )

    assert record.status == "rate_limited"
    assert record.http_status == 429
    assert record.tokens_input == 0
    assert record.tokens_output == 0
    assert record.model == "test-model"


# ---------------------------------------------------------------------------
# _process_event (streaming)
# ---------------------------------------------------------------------------


def test_process_event_message_start():
    state = {}
    msg = SimpleNamespace(
        id="msg_abc",
        model="claude-sonnet-4-20250514",
        usage=_make_usage(input_tokens=80),
        _request_id="req_abc",
    )
    event = _make_event("message_start", message=msg)
    result = _process_event(event, state)

    assert result is None
    assert state["model"] == "claude-sonnet-4-20250514"
    assert state["input_tokens"] == 80
    assert state["provider_request_id"] == "req_abc"


def test_process_event_content_block_delta():
    state = {}
    delta = SimpleNamespace(text="Hello")
    event = _make_event("content_block_delta", delta=delta)
    result = _process_event(event, state)

    assert result == "Hello"
    assert "first_token_time" in state


def test_process_event_message_delta():
    state = {}
    usage = SimpleNamespace(output_tokens=42)
    delta = SimpleNamespace(stop_reason="end_turn")
    event = _make_event("message_delta", delta=delta, usage=usage)
    result = _process_event(event, state)

    assert result is None
    assert state["output_tokens"] == 42
    assert state["stop_reason"] == "end_turn"


# ---------------------------------------------------------------------------
# Tracking never crashes user code
# ---------------------------------------------------------------------------


def test_tracking_failure_does_not_crash():
    """If _push_record fails, the user's call should still succeed."""
    response = _make_response()

    with patch("caliper._patch_helpers.push_record", side_effect=RuntimeError("buffer broken")):
        # _build_record_from_response should work fine
        record = _build_record_from_response(response, {}, duration_ms=100, request_meta={}, request_id="test-uuid")
        assert record is not None

    # The try/except in _tracked_create ensures errors are swallowed.
    # We verify the helper functions themselves don't crash.


def test_metadata_attached_to_record():
    """Feature metadata should be merged into the record."""
    response = _make_response()
    with features(feature="chat", user_id="42"):
        record = _build_record_from_response(
            response, {}, duration_ms=100, request_meta={"campaign": "q4"}, request_id="test-uuid"
        )
    assert record.metadata == {"feature": "chat", "user_id": "42", "campaign": "q4"}


def test_build_record_includes_request_id():
    response = _make_response()
    record = _build_record_from_response(response, {}, duration_ms=100, request_meta={}, request_id="my-req-id")
    assert record.request_id == "my-req-id"


def test_linked_request_id_from_features_context():
    """When previous_request is set in features, records should include linked_request_id."""
    response = _make_response()
    with features(previous_request="prev-req-id"):
        record = _build_record_from_response(response, {}, duration_ms=100, request_meta={}, request_id="test-uuid")
    assert record.linked_request_id == "prev-req-id"


def test_linked_request_id_none_without_features():
    response = _make_response()
    record = _build_record_from_response(response, {}, duration_ms=100, request_meta={}, request_id="test-uuid")
    assert record.linked_request_id is None


# ---------------------------------------------------------------------------
# _extract_tools_provided
# ---------------------------------------------------------------------------


def test_extract_tools_provided_with_tools():
    kwargs = {"tools": [{"name": "get_weather", "description": "..."}, {"name": "search", "description": "..."}]}
    result = _extract_tools_provided(kwargs)
    assert result == ["get_weather", "search"]


def test_extract_tools_provided_no_tools():
    assert _extract_tools_provided({}) is None
    assert _extract_tools_provided({"tools": []}) is None
    assert _extract_tools_provided({"tools": None}) is None


# ---------------------------------------------------------------------------
# _extract_tool_calls
# ---------------------------------------------------------------------------


def test_extract_tool_calls_with_tool_use():
    response = SimpleNamespace(
        content=[
            SimpleNamespace(type="text", text="Let me check the weather."),
            SimpleNamespace(type="tool_use", name="get_weather", id="tu_1", input={"city": "NYC"}),
        ]
    )
    result = _extract_tool_calls(response)
    assert result is not None
    import json

    parsed = json.loads(result)
    assert len(parsed) == 1
    assert parsed[0]["name"] == "get_weather"
    assert parsed[0]["id"] == "tu_1"
    assert parsed[0]["input"] == {"city": "NYC"}


def test_extract_tool_calls_no_tool_use():
    response = SimpleNamespace(content=[SimpleNamespace(type="text", text="Hello!")])
    assert _extract_tool_calls(response) is None


def test_extract_tool_calls_no_content():
    response = SimpleNamespace(content=None)
    assert _extract_tool_calls(response) is None


# ---------------------------------------------------------------------------
# capture_tools integration with _build_record_from_response
# ---------------------------------------------------------------------------


def test_build_record_capture_tools_enabled():
    set_capture_tools(True)
    try:
        response = SimpleNamespace(
            model="claude-sonnet-4-20250514",
            usage=_make_usage(),
            _request_id="req_1",
            content=[
                SimpleNamespace(type="tool_use", name="get_weather", id="tu_1", input={"city": "NYC"}),
            ],
        )
        kwargs = {"model": "claude-sonnet-4-20250514", "tools": [{"name": "get_weather", "description": "..."}]}
        record = _build_record_from_response(response, kwargs, duration_ms=100, request_meta={}, request_id="test-uuid")
        assert record.tools_provided == ["get_weather"]
        assert record.tool_calls is not None
    finally:
        set_capture_tools(False)


def test_build_record_capture_tools_disabled():
    set_capture_tools(False)
    response = SimpleNamespace(
        model="claude-sonnet-4-20250514",
        usage=_make_usage(),
        _request_id="req_1",
        content=[
            SimpleNamespace(type="tool_use", name="get_weather", id="tu_1", input={"city": "NYC"}),
        ],
    )
    kwargs = {"model": "claude-sonnet-4-20250514", "tools": [{"name": "get_weather", "description": "..."}]}
    record = _build_record_from_response(response, kwargs, duration_ms=100, request_meta={}, request_id="test-uuid")
    assert record.tools_provided is None
    assert record.tool_calls is None
