import unittest
from unittest.mock import patch

import pytest

from litellm.integrations.levo.levo import LevoConfig, LevoLogger
from litellm.integrations.opentelemetry import OpenTelemetryConfig

# Try to import OpenTelemetry packages, skip tests if not available
try:
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import SimpleSpanProcessor
    from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
        InMemorySpanExporter,
    )

    OPENTELEMETRY_AVAILABLE = True
except ImportError:
    OPENTELEMETRY_AVAILABLE = False


class TestLevoConfig(unittest.TestCase):
    """Unit tests for LevoLogger configuration."""

    @patch.dict(
        "os.environ",
        {
            "LEVOAI_API_KEY": "test-api-key",
            "LEVOAI_ORG_ID": "test-org-id",
            "LEVOAI_WORKSPACE_ID": "test-workspace-id",
            "LEVOAI_COLLECTOR_URL": "https://collector.levo.ai",
        },
    )
    def test_get_levo_config_with_all_required_vars(self):
        """Test get_levo_config() with all required environment variables."""
        config = LevoLogger.get_levo_config()

        # Verify headers include all three values
        self.assertIn("Authorization=Bearer test-api-key", config.otlp_auth_headers)
        self.assertIn("x-levo-organization-id=test-org-id", config.otlp_auth_headers)
        self.assertIn("x-levo-workspace-id=test-workspace-id", config.otlp_auth_headers)

        # Verify endpoint uses provided collector URL exactly as-is
        self.assertEqual(config.endpoint, "https://collector.levo.ai")

        # Verify protocol is otlp_http
        self.assertEqual(config.protocol, "otlp_http")

    @patch.dict(
        "os.environ",
        {
            "LEVOAI_API_KEY": "test-api-key",
            "LEVOAI_ORG_ID": "test-org-id",
            "LEVOAI_WORKSPACE_ID": "test-workspace-id",
            "LEVOAI_COLLECTOR_URL": "https://custom.collector.com",
        },
    )
    def test_get_levo_config_with_custom_collector_url(self):
        """Test get_levo_config() with custom collector URL."""
        config = LevoLogger.get_levo_config()

        # Verify endpoint uses custom URL exactly as provided
        self.assertEqual(config.endpoint, "https://custom.collector.com")
        self.assertEqual(config.protocol, "otlp_http")

    @patch.dict("os.environ", {}, clear=True)
    def test_get_levo_config_missing_api_key(self):
        """Test get_levo_config() raises ValueError when LEVOAI_API_KEY is missing."""
        with pytest.raises(ValueError, match="LEVOAI_API_KEY"):
            LevoLogger.get_levo_config()

    @patch.dict(
        "os.environ",
        {
            "LEVOAI_API_KEY": "test-api-key",
        },
        clear=True,
    )
    def test_get_levo_config_missing_org_id(self):
        """Test get_levo_config() raises ValueError when LEVOAI_ORG_ID is missing."""
        with pytest.raises(ValueError, match="LEVOAI_ORG_ID"):
            LevoLogger.get_levo_config()

    @patch.dict(
        "os.environ",
        {
            "LEVOAI_API_KEY": "test-api-key",
            "LEVOAI_ORG_ID": "test-org-id",
        },
        clear=True,
    )
    def test_get_levo_config_missing_workspace_id(self):
        """Test get_levo_config() raises ValueError when LEVOAI_WORKSPACE_ID is missing."""
        with pytest.raises(ValueError, match="LEVOAI_WORKSPACE_ID"):
            LevoLogger.get_levo_config()

    @patch.dict(
        "os.environ",
        {
            "LEVOAI_API_KEY": "test-api-key",
            "LEVOAI_ORG_ID": "test-org-id",
            "LEVOAI_WORKSPACE_ID": "test-workspace-id",
        },
        clear=True,
    )
    def test_get_levo_config_missing_collector_url(self):
        """Test get_levo_config() raises ValueError when LEVOAI_COLLECTOR_URL is missing."""
        with pytest.raises(ValueError, match="LEVOAI_COLLECTOR_URL"):
            LevoLogger.get_levo_config()

    @patch.dict(
        "os.environ",
        {
            "LEVOAI_API_KEY": "test-api-key",
            "LEVOAI_ORG_ID": "test-org-id",
            "LEVOAI_WORKSPACE_ID": "test-workspace-id",
            "LEVOAI_COLLECTOR_URL": "http://localhost:4318",
        },
    )
    def test_get_levo_config_with_http_endpoint(self):
        """Test get_levo_config() with HTTP endpoint."""
        config = LevoLogger.get_levo_config()

        # Should use HTTP endpoint exactly as provided
        self.assertEqual(config.endpoint, "http://localhost:4318")
        self.assertEqual(config.protocol, "otlp_http")

    @patch.dict(
        "os.environ",
        {
            "LEVOAI_API_KEY": "test-api-key",
            "LEVOAI_ORG_ID": "test-org-id",
            "LEVOAI_WORKSPACE_ID": "test-workspace-id",
            "LEVOAI_COLLECTOR_URL": "https://collector.levo.ai",
        },
    )
    def test_levo_config_headers_format(self):
        """Test that OTLP headers are formatted correctly."""
        config = LevoLogger.get_levo_config()

        # Verify headers contain all required parts
        self.assertIn("Authorization=Bearer test-api-key", config.otlp_auth_headers)
        self.assertIn("x-levo-organization-id=test-org-id", config.otlp_auth_headers)
        self.assertIn("x-levo-workspace-id=test-workspace-id", config.otlp_auth_headers)

        # Verify headers are comma-separated
        header_parts = config.otlp_auth_headers.split(",")
        self.assertEqual(len(header_parts), 3)


class TestLevoIntegration(unittest.TestCase):
    """Integration tests for LevoLogger."""

    @patch.dict(
        "os.environ",
        {
            "LEVOAI_API_KEY": "test-api-key",
            "LEVOAI_ORG_ID": "test-org-id",
            "LEVOAI_WORKSPACE_ID": "test-workspace-id",
            "LEVOAI_COLLECTOR_URL": "https://collector.levo.ai",
        },
    )
    @pytest.mark.skipif(
        not OPENTELEMETRY_AVAILABLE, reason="OpenTelemetry packages not installed"
    )
    @patch(
        "litellm.integrations.opentelemetry.OpenTelemetry._init_otel_logger_on_litellm_proxy"
    )
    @pytest.mark.asyncio
    async def test_levo_logger_health_check_healthy(self, mock_init_proxy):
        """Test health check returns healthy status when config is valid."""
        # Mock the proxy initialization to avoid importing proxy code
        mock_init_proxy.return_value = None

        config = LevoLogger.get_levo_config()
        otel_config = OpenTelemetryConfig(
            exporter=config.protocol,
            endpoint=config.endpoint,
            headers=config.otlp_auth_headers,
        )

        # Create tracer provider with in-memory exporter
        tracer_provider = TracerProvider()
        tracer_provider.add_span_processor(SimpleSpanProcessor(InMemorySpanExporter()))

        levo_logger = LevoLogger(
            config=otel_config, callback_name="levo", tracer_provider=tracer_provider
        )

        # Run health check
        result = await levo_logger.async_health_check()

        self.assertEqual(result["status"], "healthy")
        self.assertIn("message", result)

    @patch.dict("os.environ", {}, clear=True)
    def test_levo_logger_health_check_unhealthy(self):
        """Test health check returns unhealthy status when required vars are missing."""
        # Try to create logger without required env vars
        # This should fail during config, but we can test health check logic
        with pytest.raises(ValueError, match='LEVOAI_API_KEY environment variable is required for Levo'):
            LevoLogger.get_levo_config()

    @patch.dict(
        "os.environ",
        {
            "LEVOAI_API_KEY": "test-api-key",
            "LEVOAI_ORG_ID": "test-org-id",
            "LEVOAI_WORKSPACE_ID": "test-workspace-id",
            "LEVOAI_COLLECTOR_URL": "https://collector.levo.ai",
        },
    )
    @pytest.mark.skipif(
        not OPENTELEMETRY_AVAILABLE, reason="OpenTelemetry packages not installed"
    )
    @patch(
        "litellm.integrations.opentelemetry.OpenTelemetry._init_otel_logger_on_litellm_proxy"
    )
    def test_levo_logger_callback_name(self, mock_init_proxy):
        """Test that callback_name is properly set and used."""
        # Mock the proxy initialization to avoid importing proxy code
        mock_init_proxy.return_value = None

        config = LevoLogger.get_levo_config()
        otel_config = OpenTelemetryConfig(
            exporter=config.protocol,
            endpoint=config.endpoint,
            headers=config.otlp_auth_headers,
        )

        # Create tracer provider with in-memory exporter
        tracer_provider = TracerProvider()
        tracer_provider.add_span_processor(SimpleSpanProcessor(InMemorySpanExporter()))

        levo_logger = LevoLogger(
            config=otel_config, callback_name="levo", tracer_provider=tracer_provider
        )

        # Verify callback_name attribute
        self.assertEqual(levo_logger.callback_name, "levo")


@pytest.mark.parametrize(
    "env_vars, expected_headers_contains, expected_endpoint, expected_protocol",
    [
        pytest.param(
            {
                "LEVOAI_API_KEY": "test-key",
                "LEVOAI_ORG_ID": "test-org",
                "LEVOAI_WORKSPACE_ID": "test-workspace",
                "LEVOAI_COLLECTOR_URL": "https://collector.levo.ai",
            },
            [
                "Authorization=Bearer test-key",
                "x-levo-organization-id=test-org",
                "x-levo-workspace-id=test-workspace",
            ],
            "https://collector.levo.ai",
            "otlp_http",
            id="collector URL with all required vars",
        ),
        pytest.param(
            {
                "LEVOAI_API_KEY": "key-123",
                "LEVOAI_ORG_ID": "org-456",
                "LEVOAI_WORKSPACE_ID": "workspace-789",
                "LEVOAI_COLLECTOR_URL": "https://custom.example.com",
            },
            [
                "Authorization=Bearer key-123",
                "x-levo-organization-id=org-456",
                "x-levo-workspace-id=workspace-789",
            ],
            "https://custom.example.com",
            "otlp_http",
            id="custom collector URL",
        ),
        pytest.param(
            {
                "LEVOAI_API_KEY": "key-123",
                "LEVOAI_ORG_ID": "org-456",
                "LEVOAI_WORKSPACE_ID": "workspace-789",
                "LEVOAI_COLLECTOR_URL": "http://localhost:9999",
            },
            ["Authorization=Bearer key-123"],
            "http://localhost:9999",
            "otlp_http",
            id="custom HTTP endpoint",
        ),
    ],
)
def test_get_levo_config_parametrized(
    monkeypatch,
    env_vars,
    expected_headers_contains,
    expected_endpoint,
    expected_protocol,
):
    """Parametrized tests for get_levo_config() with various configurations."""
    # Clear all Levo-related env vars first to ensure clean state
    for key in [
        "LEVOAI_API_KEY",
        "LEVOAI_ORG_ID",
        "LEVOAI_WORKSPACE_ID",
        "LEVOAI_COLLECTOR_URL",
        "LEVOAI_ENV_NAME",
    ]:
        monkeypatch.delenv(key, raising=False)

    for key, value in env_vars.items():
        monkeypatch.setenv(key, value)

    config = LevoLogger.get_levo_config()

    assert isinstance(config, LevoConfig)
    assert config.endpoint == expected_endpoint
    assert config.protocol == expected_protocol

    # Verify all expected header parts are present
    for header_part in expected_headers_contains:
        assert header_part in config.otlp_auth_headers


@pytest.mark.parametrize(
    "missing_var",
    [
        pytest.param("LEVOAI_API_KEY", id="missing API key"),
        pytest.param("LEVOAI_ORG_ID", id="missing org ID"),
        pytest.param("LEVOAI_WORKSPACE_ID", id="missing workspace ID"),
        pytest.param("LEVOAI_COLLECTOR_URL", id="missing collector URL"),
    ],
)
def test_get_levo_config_missing_required_vars(monkeypatch, missing_var):
    """Test that missing required environment variables raise ValueError."""
    # Clear all Levo-related env vars
    for key in [
        "LEVOAI_API_KEY",
        "LEVOAI_ORG_ID",
        "LEVOAI_WORKSPACE_ID",
        "LEVOAI_COLLECTOR_URL",
    ]:
        monkeypatch.delenv(key, raising=False)

    # Set all required vars except the missing one
    required_vars = {
        "LEVOAI_API_KEY": "test-key",
        "LEVOAI_ORG_ID": "test-org",
        "LEVOAI_WORKSPACE_ID": "test-workspace",
        "LEVOAI_COLLECTOR_URL": "https://collector.levo.ai",
    }
    required_vars.pop(missing_var)

    for key, value in required_vars.items():
        monkeypatch.setenv(key, value)

    with pytest.raises(ValueError, match=missing_var):
        LevoLogger.get_levo_config()


if __name__ == "__main__":
    unittest.main()
