"""
Unit tests for ProxyLogging._init_litellm_callbacks.

Validates that string callbacks in litellm.callbacks are replaced in-place
with their initialized instances, preventing duplicate entries (string + instance)
that caused double-counting of metrics like litellm_proxy_total_requests_metric.
"""

from typing import List, Union
from unittest.mock import MagicMock, patch

import pytest

import litellm
from litellm.integrations.custom_logger import CustomLogger


class FakeCustomLogger(CustomLogger):
    """A minimal CustomLogger subclass for testing."""

    pass


class TestInitLitellmCallbacks:
    """Tests for ProxyLogging._init_litellm_callbacks."""

    def _make_proxy_logging(self):
        """Create a ProxyLogging instance with mocked dependencies."""
        from litellm.proxy.utils import ProxyLogging

        mock_cache = MagicMock()
        proxy_logging = ProxyLogging(user_api_key_cache=mock_cache)
        return proxy_logging

    @patch(
        "litellm.proxy.utils.ProxyLogging._add_proxy_hooks",
        new_callable=lambda: lambda self, *a, **kw: None,
    )
    def test_should_replace_string_callback_with_instance(self, _mock_hooks):
        """
        When litellm.callbacks contains a string callback (e.g. "lago"),
        _init_litellm_callbacks should replace the string with the initialized
        CustomLogger instance, not leave both the string and instance in the list.
        """
        fake_logger = FakeCustomLogger()

        # Start with a string callback in litellm.callbacks
        litellm.callbacks = ["lago"]  # type: ignore

        proxy_logging = self._make_proxy_logging()

        with patch(
            "litellm.litellm_core_utils.litellm_logging._init_custom_logger_compatible_class",
            return_value=fake_logger,
        ):
            proxy_logging._init_litellm_callbacks(llm_router=None)

        # The string "lago" should be replaced by the instance, not appended
        string_entries = [c for c in litellm.callbacks if isinstance(c, str)]
        instance_entries = [
            c for c in litellm.callbacks if isinstance(c, FakeCustomLogger)
        ]

        assert (
            len(string_entries) == 0
        ), f"String callbacks should have been replaced, but found: {string_entries}"
        assert (
            len(instance_entries) == 1
        ), f"Expected exactly one FakeCustomLogger instance, found {len(instance_entries)}"
        assert instance_entries[0] is fake_logger

        # Clean up
        litellm.callbacks = []  # type: ignore

    @patch(
        "litellm.proxy.utils.ProxyLogging._add_proxy_hooks",
        new_callable=lambda: lambda self, *a, **kw: None,
    )
    def test_should_not_duplicate_existing_instance_callbacks(self, _mock_hooks):
        """
        When litellm.callbacks already contains a CustomLogger instance (not a string),
        _init_litellm_callbacks should not create a duplicate.
        """
        existing_logger = FakeCustomLogger()

        litellm.callbacks = [existing_logger]  # type: ignore

        proxy_logging = self._make_proxy_logging()

        proxy_logging._init_litellm_callbacks(llm_router=None)

        # Count how many FakeCustomLogger instances are in litellm.callbacks
        instance_count = sum(
            1 for c in litellm.callbacks if isinstance(c, FakeCustomLogger)
        )
        assert instance_count == 1, (
            f"Expected exactly 1 FakeCustomLogger instance, found {instance_count}. "
            f"litellm.callbacks = {litellm.callbacks}"
        )

        # Clean up
        litellm.callbacks = []  # type: ignore

    @patch(
        "litellm.proxy.utils.ProxyLogging._add_proxy_hooks",
        new_callable=lambda: lambda self, *a, **kw: None,
    )
    def test_should_handle_unrecognized_string_callback(self, _mock_hooks):
        """
        When _init_custom_logger_compatible_class returns None for a string callback,
        the string should remain in litellm.callbacks (not crash).
        """
        litellm.callbacks = ["unknown_callback"]  # type: ignore

        proxy_logging = self._make_proxy_logging()

        with patch(
            "litellm.litellm_core_utils.litellm_logging._init_custom_logger_compatible_class",
            return_value=None,
        ):
            proxy_logging._init_litellm_callbacks(llm_router=None)

        # The unknown string callback should still be there (not replaced, not crashed)
        assert "unknown_callback" in litellm.callbacks

        # Clean up
        litellm.callbacks = []  # type: ignore

    @patch(
        "litellm.proxy.utils.ProxyLogging._add_proxy_hooks",
        new_callable=lambda: lambda self, *a, **kw: None,
    )
    def test_should_replace_multiple_string_callbacks(self, _mock_hooks):
        """
        When litellm.callbacks contains multiple string callbacks,
        each should be replaced with its corresponding initialized instance.
        """
        fake_logger_a = FakeCustomLogger()
        fake_logger_b = FakeCustomLogger()

        litellm.callbacks = ["callback_a", "callback_b"]  # type: ignore

        proxy_logging = self._make_proxy_logging()

        call_count = 0

        def mock_init_class(callback_name, **kwargs):
            nonlocal call_count
            call_count += 1
            if call_count == 1:
                return fake_logger_a
            return fake_logger_b

        with patch(
            "litellm.litellm_core_utils.litellm_logging._init_custom_logger_compatible_class",
            side_effect=mock_init_class,
        ):
            proxy_logging._init_litellm_callbacks(llm_router=None)

        string_entries = [c for c in litellm.callbacks if isinstance(c, str)]
        instance_entries = [
            c for c in litellm.callbacks if isinstance(c, FakeCustomLogger)
        ]

        assert (
            len(string_entries) == 0
        ), f"All string callbacks should have been replaced: {string_entries}"
        assert (
            len(instance_entries) == 2
        ), f"Expected 2 FakeCustomLogger instances, found {len(instance_entries)}"
        assert instance_entries[0] is fake_logger_a
        assert instance_entries[1] is fake_logger_b

        # Clean up
        litellm.callbacks = []  # type: ignore
