"""
Test that metadata is passed to custom callbacks during chat completion calls to codex models.

Fixes issue: Metadata is no longer passed to custom callback during chat completion
calls to codex models (#21204)

Codex models (gpt-5.1-codex, gpt-5.2-codex) use mode=responses and route through
responses_api_bridge. The bridge converts metadata to litellm_metadata. This test
verifies metadata is preserved for custom callbacks via kwargs['litellm_params']['metadata'].
"""

import asyncio
import os
import sys
from typing import Optional
from unittest.mock import AsyncMock, patch

sys.path.insert(0, os.path.abspath("../../.."))

import pytest

import litellm
from litellm.integrations.custom_logger import CustomLogger


def _make_mock_http_response(response_dict: dict):
    """Create a mock HTTP response that returns response_dict from .json()."""

    class MockResponse:
        def __init__(self, json_data, status_code=200):
            self._json_data = json_data
            self.status_code = status_code
            self.text = str(json_data)
            self.headers = {}

        def json(self):
            return self._json_data

    return MockResponse(response_dict, 200)


class MetadataCaptureCallback(CustomLogger):
    """Custom callback that captures kwargs passed to async_log_success_event."""

    def __init__(self):
        self.captured_kwargs: Optional[dict] = None
        self.event = asyncio.Event()

    async def async_log_success_event(self, kwargs, response_obj, start_time, end_time):
        self.captured_kwargs = kwargs
        self.event.set()


@pytest.mark.asyncio
async def test_metadata_passed_to_custom_callback_codex_models():
    """
    Test that metadata passed to completion() is available in custom callback
    when using codex models (responses API bridge path).

    Codex models have mode=responses and route through responses_api_bridge,
    which passes litellm_metadata. The fix ensures this is preserved as
    litellm_params.metadata for callback compatibility.
    """
    from litellm.types.llms.openai import ResponsesAPIResponse

    mock_response = ResponsesAPIResponse.model_construct(
        id="resp-test",
        created_at=0,
        output=[
            {
                "type": "message",
                "id": "msg-1",
                "status": "completed",
                "role": "assistant",
                "content": [{"type": "output_text", "text": "Hello!"}],
            }
        ],
        object="response",
        model="gpt-5.1-codex",
        status="completed",
        usage={
            "input_tokens": 5,
            "output_tokens": 10,
            "total_tokens": 15,
        },
    )

    test_metadata = {"foo": "bar", "trace_id": "test-123"}
    callback = MetadataCaptureCallback()
    original_callbacks = litellm.callbacks.copy() if litellm.callbacks else []
    litellm.callbacks = [callback]

    try:
        with patch(
            "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
            new_callable=AsyncMock,
        ) as mock_post:
            mock_post.return_value = _make_mock_http_response(
                mock_response.model_dump()
            )
            # gpt-5.1-codex has mode=responses - routes through responses bridge
            await litellm.acompletion(
                model="gpt-5.1-codex",
                messages=[{"role": "user", "content": "Hello"}],
                metadata=test_metadata,
            )

        await asyncio.wait_for(callback.event.wait(), timeout=5.0)

        assert callback.captured_kwargs is not None, "Callback should have been invoked"

        litellm_params = callback.captured_kwargs.get("litellm_params", {})
        metadata = litellm_params.get("metadata") or {}

        assert "foo" in metadata, "metadata['foo'] should be accessible in callback"
        assert metadata["foo"] == "bar"
        assert metadata.get("trace_id") == "test-123"
    finally:
        litellm.callbacks = original_callbacks


@pytest.mark.asyncio
async def test_metadata_passed_via_litellm_metadata_responses_api():
    """
    Test that when calling responses() directly with litellm_metadata,
    metadata is preserved for custom callbacks.

    Uses HTTP mock since mock_response returns early before update_environment_variables.
    """
    from litellm.types.llms.openai import ResponsesAPIResponse

    mock_response = ResponsesAPIResponse.model_construct(
        id="resp-test-2",
        created_at=0,
        output=[
            {
                "type": "message",
                "id": "msg-2",
                "status": "completed",
                "role": "assistant",
                "content": [{"type": "output_text", "text": "Hi there!"}],
            }
        ],
        object="response",
        model="gpt-4o",
        status="completed",
        usage={
            "input_tokens": 2,
            "output_tokens": 3,
            "total_tokens": 5,
        },
    )

    test_metadata = {"request_id": "req-456"}
    callback = MetadataCaptureCallback()
    original_callbacks = litellm.callbacks.copy() if litellm.callbacks else []
    litellm.callbacks = [callback]

    try:
        with patch(
            "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
            new_callable=AsyncMock,
        ) as mock_post:
            mock_post.return_value = _make_mock_http_response(
                mock_response.model_dump()
            )
            await litellm.aresponses(
                model="gpt-4o",
                input="hi",
                litellm_metadata=test_metadata,
            )

        await asyncio.wait_for(callback.event.wait(), timeout=5.0)

        assert callback.captured_kwargs is not None

        litellm_params = callback.captured_kwargs.get("litellm_params", {})
        metadata = litellm_params.get("metadata") or {}

        assert "request_id" in metadata
        assert metadata["request_id"] == "req-456"
    finally:
        litellm.callbacks = original_callbacks
