from __future__ import annotations

from types import SimpleNamespace

import pytest

from src import observability


def test_metrics_collector_records_prometheus_metrics_when_available() -> None:
    if not observability.PROMETHEUS_AVAILABLE:
        pytest.skip("prometheus_client is not installed in this environment")

    collector = observability.MetricsCollector()
    collector.record_api_request(
        method="GET",
        endpoint="me/adaccounts",
        status_code=200,
        duration=0.25,
        ad_account_id="act_123",
    )
    collector.record_cache_metrics(cache_type="response_cache", endpoint="me/adaccounts", hit=True)
    collector.record_rate_limit_metrics(endpoint="me/adaccounts", remaining=12, user_id="act_123")
    collector.record_error(error_type="MetaApiError", endpoint="me/adaccounts", ad_account_id="act_123")

    metrics_text = collector.get_metrics()
    assert "meta_ads_api_requests_total" in metrics_text
    assert "meta_ads_cache_hits_total" in metrics_text
    assert "meta_ads_rate_limit_remaining" in metrics_text
    assert "meta_ads_errors_total" in metrics_text


def test_metrics_collector_noops_when_prometheus_unavailable(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(observability, "PROMETHEUS_AVAILABLE", False)
    collector = observability.MetricsCollector()

    # Should not raise when Prometheus dependencies are unavailable.
    collector.record_api_request("GET", "campaigns", 200, 0.1, ad_account_id="act_1")
    collector.record_cache_metrics("response_cache", "campaigns", hit=False)
    collector.record_rate_limit_metrics("campaigns", remaining=10, user_id="act_1")
    collector.record_error("MetaApiError", "campaigns", ad_account_id="act_1")

    assert collector.get_metrics().startswith("# Prometheus client not available")


@pytest.mark.asyncio
async def test_trace_operation_is_noop_without_opentelemetry(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(observability, "OPENTELEMETRY_AVAILABLE", False)
    provider = observability.TracingProvider()

    async with provider.trace_operation("unit_test_span"):
        pass


def test_resolve_otlp_insecure_defaults_and_overrides(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.delenv("OTEL_EXPORTER_OTLP_INSECURE", raising=False)

    assert observability._resolve_otlp_insecure("https://otel.example:4317") is False
    assert observability._resolve_otlp_insecure("http://otel.example:4317") is True

    monkeypatch.setenv("OTEL_EXPORTER_OTLP_INSECURE", "true")
    assert observability._resolve_otlp_insecure("https://otel.example:4317") is True

    # Explicit configuration takes precedence over env defaults.
    assert (
        observability._resolve_otlp_insecure(
            "https://otel.example:4317",
            explicit=False,
        )
        is False
    )


def test_tracing_provider_uses_tls_for_https_endpoint_by_default(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(observability, "OPENTELEMETRY_AVAILABLE", True)
    monkeypatch.delenv("OTEL_EXPORTER_OTLP_INSECURE", raising=False)

    captured: dict[str, object] = {}

    class FakeOTLPSpanExporter:
        def __init__(self, *, endpoint: str, insecure: bool):
            captured["endpoint"] = endpoint
            captured["insecure"] = insecure

    class FakeTracerProvider:
        def __init__(self, resource):
            self.resource = resource

        def add_span_processor(self, _processor) -> None:
            return None

    class FakeBatchSpanProcessor:
        def __init__(self, _exporter):
            return None

    class FakeResource:
        @staticmethod
        def create(attributes):
            return attributes

    monkeypatch.setattr(
        observability,
        "trace",
        SimpleNamespace(
            set_tracer_provider=lambda _provider: None,
            get_tracer=lambda _name: object(),
        ),
        raising=False,
    )
    monkeypatch.setattr(observability, "OTLPSpanExporter", FakeOTLPSpanExporter, raising=False)
    monkeypatch.setattr(observability, "TracerProvider", FakeTracerProvider, raising=False)
    monkeypatch.setattr(observability, "BatchSpanProcessor", FakeBatchSpanProcessor, raising=False)
    monkeypatch.setattr(observability, "Resource", FakeResource, raising=False)

    observability.TracingProvider(otlp_endpoint="https://otel.example:4317")

    assert captured["endpoint"] == "https://otel.example:4317"
    assert captured["insecure"] is False


def test_metrics_provider_honors_explicit_otlp_insecure_setting(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(observability, "OPENTELEMETRY_AVAILABLE", True)
    monkeypatch.delenv("OTEL_EXPORTER_OTLP_INSECURE", raising=False)

    captured: dict[str, object] = {}

    class FakeOTLPMetricExporter:
        def __init__(self, *, endpoint: str, insecure: bool):
            captured["endpoint"] = endpoint
            captured["insecure"] = insecure

    class FakeMetricReader:
        def __init__(self, _exporter, export_interval_millis: int):
            captured["interval"] = export_interval_millis

    class FakeMeterProvider:
        def __init__(self, resource, metric_readers):
            self.resource = resource
            self.metric_readers = metric_readers

    class FakeResource:
        @staticmethod
        def create(attributes):
            return attributes

    monkeypatch.setattr(
        observability,
        "metrics",
        SimpleNamespace(
            set_meter_provider=lambda _provider: None,
            get_meter=lambda _name: object(),
        ),
        raising=False,
    )
    monkeypatch.setattr(observability, "OTLPMetricExporter", FakeOTLPMetricExporter, raising=False)
    monkeypatch.setattr(
        observability,
        "PeriodicExportingMetricReader",
        FakeMetricReader,
        raising=False,
    )
    monkeypatch.setattr(observability, "MeterProvider", FakeMeterProvider, raising=False)
    monkeypatch.setattr(observability, "Resource", FakeResource, raising=False)

    observability.MetricsProvider(
        otlp_endpoint="https://otel.example:4317",
        otlp_insecure=True,
    )

    assert captured["endpoint"] == "https://otel.example:4317"
    assert captured["insecure"] is True
