"""E2E tests for metric flow: SDK → Collector → GCP Cloud Monitoring.

Tests verify that metrics recorded via SDK successfully propagate through
the OTel Collector and appear in GCP Cloud Monitoring within 30 seconds.
"""

import time
import pytest
from mca_sdk import MCAClient


@pytest.mark.integration
@pytest.mark.error_scenario  # Collector logs are expected
class TestMetricsE2E:
    """End-to-end tests for metrics propagation."""

    def test_prediction_metric_propagates_to_gcp(
        self,
        docker_compose_environment,
        gcp_monitoring_client,
        gcp_project_id,
        wait_for_telemetry,
        cleanup_collector_exports,
    ):
        """Test AC1 & AC2: Prediction metric flows from SDK to GCP Cloud Monitoring.

        Given: E2E environment with SDK, Collector, and GCP mock
        When: SDK records prediction with latency
        Then: Both counter and latency metrics appear in Cloud Monitoring within 30 seconds
        """
        # Arrange: Create SDK client pointing to E2E collector
        client = MCAClient(
            service_name="e2e-test-model",
            model_id="e2e-mdl-001",
            team_name="e2e-test-team",
            model_category="internal",
            model_type="regression",
            otel_endpoint=docker_compose_environment["collector_otlp"],
            otel_protocol="http",
        )

        # Record start time for query window
        start_time = time.time()

        # Act: Record prediction via SDK
        prediction_id = "e2e-pred-001"
        latency = 0.123
        client.record_prediction(
            latency=latency,
            prediction_id=prediction_id,
            model_version="1.0.0",
            environment="test",
        )

        # Force flush to ensure immediate export
        client.shutdown()

        # Wait for telemetry propagation
        wait_for_telemetry(10, "metric propagation to file exporter")

        # Assert: Query for counter metric
        end_time = time.time()
        counter_metrics = gcp_monitoring_client.query_metrics(
            project_id=gcp_project_id,
            metric_type="mca.internal.regression.predictions",
            start_time=start_time,
            end_time=end_time,
        )

        # Verify counter metric exists
        assert (
            len(counter_metrics) > 0
        ), f"No counter metrics found. Available metrics: {counter_metrics}"

        # Verify metric attributes
        metric = counter_metrics[0]
        assert (
            "prediction.id" in metric["attributes"] or "prediction_id" in metric["attributes"]
        ), f"prediction_id not in attributes: {metric['attributes']}"
        assert (
            "model.version" in metric["attributes"] or "model_version" in metric["attributes"]
        ), f"model_version not in attributes: {metric['attributes']}"
        assert metric["value"] > 0, "Counter value should be greater than 0"

        # Query for latency histogram metric
        latency_metrics = gcp_monitoring_client.query_metrics(
            project_id=gcp_project_id,
            metric_type="mca.internal.regression.latency_seconds",
            start_time=start_time,
            end_time=end_time,
        )

        # Verify latency metric exists
        assert len(latency_metrics) > 0, "Latency histogram metric not found"

        # Verify histogram structure (has count, sum, buckets)
        latency_metric = latency_metrics[0]
        if "count" in latency_metric:  # Histogram format
            assert latency_metric["count"] > 0, "Histogram count should be > 0"
            assert latency_metric["sum"] > 0, "Histogram sum should be > 0"
            # Verify average is close to recorded latency (±50%)
            avg_latency = latency_metric["sum"] / latency_metric["count"]
            assert (
                abs(avg_latency - latency) / latency < 0.5
            ), f"Average latency {avg_latency} differs significantly from recorded {latency}"

    def test_latency_histogram_propagates_to_gcp(
        self,
        docker_compose_environment,
        gcp_monitoring_client,
        gcp_project_id,
        wait_for_telemetry,
        cleanup_collector_exports,
    ):
        """Test latency histogram distribution metric propagation.

        Given: SDK records multiple predictions with varying latencies
        When: Metrics are exported to GCP
        Then: Histogram distribution is correctly captured with proper bucketing
        """
        # Arrange
        client = MCAClient(
            service_name="e2e-latency-test",
            model_id="e2e-mdl-002",
            team_name="e2e-test-team",
            model_category="internal",
            model_type="classification",
            otel_endpoint=docker_compose_environment["collector_otlp"],
            otel_protocol="http",
        )

        start_time = time.time()

        # Act: Record multiple predictions with varying latencies
        latencies = [0.050, 0.100, 0.150, 0.200, 0.300]
        for idx, latency in enumerate(latencies):
            client.record_prediction(
                latency=latency,
                prediction_id=f"e2e-pred-latency-{idx}",
                model_version="1.0.0",
            )

        client.shutdown()
        wait_for_telemetry(10, "histogram aggregation and export")

        # Assert: Verify histogram metric in GCP
        end_time = time.time()
        metrics = gcp_monitoring_client.query_metrics(
            project_id=gcp_project_id,
            metric_type="mca.internal.classification.latency_seconds",
            start_time=start_time,
            end_time=end_time,
        )

        assert len(metrics) > 0, "Histogram metric not found in GCP"

        histogram = metrics[0]
        # Verify histogram has proper structure
        assert "count" in histogram, "Histogram missing count field"
        assert "sum" in histogram, "Histogram missing sum field"
        assert histogram["count"] == len(
            latencies
        ), f"Expected {len(latencies)} observations, got {histogram['count']}"

        # Verify sum is approximately correct (within tolerance)
        expected_sum = sum(latencies)
        actual_sum = histogram["sum"]
        assert (
            abs(actual_sum - expected_sum) / expected_sum < 0.1
        ), f"Histogram sum {actual_sum} differs from expected {expected_sum}"

        # Verify bucket distribution exists
        if "bucketCounts" in histogram:
            total_bucket_count = sum(histogram["bucketCounts"])
            assert total_bucket_count >= len(
                latencies
            ), f"Bucket counts {total_bucket_count} should account for all {len(latencies)} samples"

    def test_custom_attributes_preserved_in_gcp(
        self,
        docker_compose_environment,
        gcp_monitoring_client,
        gcp_project_id,
        wait_for_telemetry,
        cleanup_collector_exports,
    ):
        """Test that custom attributes are preserved through full pipeline.

        Given: SDK records metric with custom attributes of various types
        When: Metric propagates to GCP
        Then: All custom attributes are preserved with correct types
        """
        # Arrange
        client = MCAClient(
            service_name="e2e-custom-attrs",
            model_id="e2e-mdl-003",
            team_name="e2e-test-team",
            model_category="internal",
            model_type="regression",
            otel_endpoint=docker_compose_environment["collector_otlp"],
            otel_protocol="http",
        )

        start_time = time.time()

        # Act: Record with custom attributes of different types
        prediction_id = "e2e-custom-001"
        client.record_prediction(
            latency=0.1,
            prediction_id=prediction_id,
            custom_attr_1="value1",
            custom_attr_2="value2",
            custom_int=42,
            custom_float=3.14,
        )

        client.shutdown()
        wait_for_telemetry(10, "custom attributes propagation")

        # Assert
        end_time = time.time()
        metrics = gcp_monitoring_client.query_metrics(
            project_id=gcp_project_id,
            metric_type="mca.internal.regression.predictions",
            start_time=start_time,
            end_time=end_time,
        )

        assert len(metrics) > 0, "No metrics found"

        # Find our specific metric by prediction_id
        our_metric = None
        for m in metrics:
            attrs = m["attributes"]
            pred_id = attrs.get("prediction.id") or attrs.get("prediction_id")
            if pred_id == prediction_id:
                our_metric = m
                break

        assert our_metric is not None, f"Metric with prediction_id={prediction_id} not found"

        attrs = our_metric["attributes"]

        # Verify string attributes (handle both snake_case and dot.notation)
        assert (
            attrs.get("custom_attr_1") == "value1" or attrs.get("custom.attr.1") == "value1"
        ), f"custom_attr_1 not found or incorrect: {attrs}"
        assert (
            attrs.get("custom_attr_2") == "value2" or attrs.get("custom.attr.2") == "value2"
        ), f"custom_attr_2 not found or incorrect: {attrs}"

        # Verify numeric attributes
        custom_int = attrs.get("custom_int") or attrs.get("custom.int")
        assert custom_int == 42, f"custom_int expected 42, got {custom_int}"

        custom_float = attrs.get("custom_float") or attrs.get("custom.float")
        assert custom_float == pytest.approx(
            3.14, abs=0.01
        ), f"custom_float expected ~3.14, got {custom_float}"
