"""Story 8.7: Comprehensive tests for metric filtering Views.

Tests the DEFAULT_DROPPED_PATTERNS and DEFAULT_DROP_VIEWS to ensure
high-volume auto-instrumentation metrics are properly dropped while
retaining business/model metrics.
"""

import os
import pytest
from unittest.mock import patch, MagicMock
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.metrics.export import InMemoryMetricReader
from opentelemetry.sdk.metrics import MeterProvider, Counter, Histogram
from opentelemetry.sdk.metrics.view import View
from opentelemetry.sdk.metrics._internal.aggregation import DropAggregation

from mca_sdk.core.providers import (
    setup_meter_provider,
    DEFAULT_DROPPED_PATTERNS,
    DEFAULT_DROP_VIEWS,
)


@pytest.fixture
def test_resource():
    """Create a test Resource for metrics."""
    return Resource.create({"service.name": "test-service"})


@pytest.mark.parametrize(
    "metric_name",
    [
        "process.cpu_time",
        "process.memory.usage",
        "system.cpu.utilization",
        "system.memory.available",
        "asyncio.task.count",
        "cpython.gc.count",
        "http.client.duration",
        "otel.sdk.export.duration",
        "test.my_custom_metric",
        "rpc.client.duration",
    ],
)
@patch("mca_sdk.core.providers.OTLPMetricExporter")
def test_default_views_drop_high_volume_metrics(mock_exporter, test_resource, metric_name):
    """Test that high-volume auto-instrumentation metrics are dropped.

    Story 8.7 AC1: Verify each dropped pattern individually
    Phase 8: Control metric ensures reader is functional
    """
    mock_exporter.return_value = MagicMock()

    # Use InMemoryMetricReader to inspect collected metrics
    reader = InMemoryMetricReader()

    # Create provider with default drop views
    provider = MeterProvider(
        resource=test_resource, metric_readers=[reader], views=DEFAULT_DROP_VIEWS
    )

    # Create meters for both dropped and retained metrics
    meter = provider.get_meter("test-meter")

    # Create and record the target metric (should be dropped)
    counter = meter.create_counter(metric_name, description="Metric under test")
    counter.add(1, {"test": "value"})

    # Create and record a control metric (must be retained)
    control_counter = meter.create_counter("model.predictions_total", description="Control metric")
    control_counter.add(1, {"test": "control"})

    # Force collection
    metrics_data = reader.get_metrics_data()

    # Extract all metric names from the collected data
    found_metrics = []
    if metrics_data and hasattr(metrics_data, "resource_metrics"):
        for rm in metrics_data.resource_metrics:
            for sm in rm.scope_metrics:
                for metric in sm.metrics:
                    found_metrics.append(metric.name)

    # Phase 8: Assert control metric exists (proves reader is functional)
    assert (
        "model.predictions_total" in found_metrics
    ), f"Control metric missing. Reader malfunction. Found: {found_metrics}"

    # Phase 7: Assert target metric was dropped
    assert (
        metric_name not in found_metrics
    ), f"Metric {metric_name} should be dropped but was found in {found_metrics}"


@pytest.mark.parametrize(
    "metric_name",
    [
        "model.predictions_total",
        "model.latency_seconds",
        "llm.tokens_total",
        "custom.business_metric",
    ],
)
@patch("mca_sdk.core.providers.OTLPMetricExporter")
def test_default_views_retain_business_metrics(mock_exporter, test_resource, metric_name):
    """Test that business/model metrics are retained.

    Story 8.7 AC2: Verify business metrics are not affected by filtering
    """
    mock_exporter.return_value = MagicMock()

    reader = InMemoryMetricReader()
    provider = MeterProvider(
        resource=test_resource, metric_readers=[reader], views=DEFAULT_DROP_VIEWS
    )

    meter = provider.get_meter("test-meter")
    counter = meter.create_counter(metric_name, description="Business metric")
    counter.add(42, {"test": "business"})

    metrics_data = reader.get_metrics_data()

    found_metrics = []
    if metrics_data and hasattr(metrics_data, "resource_metrics"):
        for rm in metrics_data.resource_metrics:
            for sm in rm.scope_metrics:
                for metric in sm.metrics:
                    found_metrics.append(metric.name)

    assert (
        metric_name in found_metrics
    ), f"Business metric {metric_name} should be retained but was not found"


@patch("mca_sdk.core.providers.OTLPMetricExporter")
@patch("mca_sdk.core.providers.PeriodicExportingMetricReader")
def test_disable_filtering_via_environment_variable(mock_reader, mock_exporter, test_resource):
    """Test MCA_DISABLE_METRIC_FILTERING environment variable.

    Story 8.7 Remediation Phase 1: Configurable filtering escape hatch
    """
    mock_exporter.return_value = MagicMock()
    mock_reader_instance = MagicMock()
    mock_reader.return_value = mock_reader_instance

    # Enable filtering bypass
    with patch.dict(os.environ, {"MCA_DISABLE_METRIC_FILTERING": "true"}):
        provider, *_ = setup_meter_provider(resource=test_resource)

        # Verify provider was created without drop views
        # Check that views list is empty or only contains custom views
        assert provider is not None


@patch("mca_sdk.core.providers.OTLPMetricExporter")
@patch("mca_sdk.core.providers.PeriodicExportingMetricReader")
def test_custom_views_are_merged_with_defaults(mock_reader, mock_exporter, test_resource):
    """Test that custom views parameter is merged with default drop views.

    Story 8.7 Remediation Phase 4: Support custom user views
    """
    mock_exporter.return_value = MagicMock()
    mock_reader_instance = MagicMock()
    mock_reader.return_value = mock_reader_instance

    # Create a custom view
    custom_view = View(instrument_name="custom.metric", aggregation=DropAggregation())

    # Call setup_meter_provider with custom views
    provider, *_ = setup_meter_provider(resource=test_resource, views=[custom_view])

    # Verify provider was created successfully
    assert provider is not None

    # Verify views were applied (both custom and defaults)
    # The provider should have len(DEFAULT_DROP_VIEWS) + 1 views
    assert hasattr(provider, "_sdk_config")


def test_default_dropped_patterns_constant():
    """Test that DEFAULT_DROPPED_PATTERNS is defined correctly.

    Story 8.7 AC3: Verify pattern list completeness
    """
    expected_patterns = [
        "process.*",
        "system.*",
        "asyncio.*",
        "cpython.*",
        "http.client.*",
        "otel.sdk.*",
        "test.*",
        "rpc.*",
    ]

    assert DEFAULT_DROPPED_PATTERNS == expected_patterns


def test_default_drop_views_are_pre_built():
    """Test that DEFAULT_DROP_VIEWS are constructed correctly.

    Story 8.7 Remediation Phase 5: Module-level constant optimization
    """
    assert len(DEFAULT_DROP_VIEWS) == len(DEFAULT_DROPPED_PATTERNS)

    for view, pattern in zip(DEFAULT_DROP_VIEWS, DEFAULT_DROPPED_PATTERNS):
        assert isinstance(view, View)
        # Verify view targets the correct pattern
        assert view._instrument_name == pattern
