"""
Tests for Prometheus metrics functionality.

Tests cover:
- MetricsCollector context manager
- Metric recording (counters, gauges, histograms)
- Normalization tracking
- Hook execution tracking
- DLQ metrics
"""

import pytest
import time

from atomingest.observability.metrics import (
    MetricsCollector,
    track_normalization,
    track_hook_execution,
    record_dlq_entry,
    set_pipeline_info,
    get_metrics,
    is_prometheus_available,
    PROMETHEUS_AVAILABLE,
)


class TestPrometheusAvailability:
    """Tests for Prometheus availability detection."""

    def test_is_prometheus_available(self):
        """Test that we can detect if prometheus_client is installed."""
        result = is_prometheus_available()
        assert isinstance(result, bool)
        # Should match the module-level constant
        assert result == PROMETHEUS_AVAILABLE


class TestMetricsCollector:
    """Tests for MetricsCollector context manager."""

    def test_metrics_collector_initialization(self):
        """Test that MetricsCollector initializes correctly."""
        collector = MetricsCollector("test_table")

        assert collector.table_name == "test_table"
        assert collector.start_time is None
        assert collector.records_processed == 0
        assert collector.records_failed == 0

    def test_metrics_collector_context_manager(self):
        """Test that MetricsCollector works as context manager."""
        with MetricsCollector("test_table") as collector:
            assert collector.start_time is not None
            assert collector.table_name == "test_table"

    def test_metrics_collector_records_duration(self):
        """Test that MetricsCollector records duration."""
        with MetricsCollector("test_table") as collector:
            start = collector.start_time
            time.sleep(0.01)  # Small delay

        # Duration should have been recorded (we can't check the actual metric without prometheus)
        assert start is not None

    def test_record_success(self):
        """Test recording successful records."""
        collector = MetricsCollector("test_table")

        collector.record_success(5)
        assert collector.records_processed == 5

        collector.record_success(3)
        assert collector.records_processed == 8

        collector.record_success()  # Default 1
        assert collector.records_processed == 9

    def test_record_failure(self):
        """Test recording failed records."""
        collector = MetricsCollector("test_table")

        collector.record_failure(2)
        assert collector.records_failed == 2

        collector.record_failure()  # Default 1
        assert collector.records_failed == 3

    def test_record_batch(self):
        """Test recording batch sizes."""
        collector = MetricsCollector("test_table")

        # Should not raise exception
        collector.record_batch(100)
        collector.record_batch(250)
        collector.record_batch(500)

    def test_metrics_collector_full_flow(self):
        """Test full flow of metrics collection."""
        with MetricsCollector("users") as collector:
            # Simulate processing records
            collector.record_batch(100)
            collector.record_success(95)
            collector.record_failure(5)

            time.sleep(0.01)

        # Metrics should have been recorded
        assert collector.records_processed == 95
        assert collector.records_failed == 5


class TestTrackNormalization:
    """Tests for normalization tracking."""

    def test_track_normalization_context_manager(self):
        """Test that track_normalization works as context manager."""
        with track_normalization("test_table"):
            time.sleep(0.01)

        # Should complete without errors

    def test_track_normalization_with_exception(self):
        """Test that track_normalization handles exceptions."""
        try:
            with track_normalization("test_table"):
                raise ValueError("Test error")
        except ValueError:
            pass  # Exception should be propagated

        # Metric should still be recorded even with exception


class TestTrackHookExecution:
    """Tests for hook execution tracking."""

    def test_track_hook_execution_context_manager(self):
        """Test that track_hook_execution works as context manager."""
        with track_hook_execution("pre_save", "MyHook"):
            time.sleep(0.01)

        # Should complete without errors

    def test_track_hook_execution_with_different_types(self):
        """Test tracking different hook types."""
        with track_hook_execution("pre_save", "ValidationHook"):
            pass

        with track_hook_execution("post_save", "NotificationHook"):
            pass

        with track_hook_execution("on_error", "DLQHook"):
            pass


class TestDLQMetrics:
    """Tests for DLQ metrics recording."""

    def test_record_dlq_entry(self):
        """Test recording DLQ entries."""
        # Should not raise exception
        record_dlq_entry("test_table", "validation_error")
        record_dlq_entry("test_table", "database_error")
        record_dlq_entry("users", "type_error")


class TestPipelineInfo:
    """Tests for pipeline information."""

    def test_set_pipeline_info(self):
        """Test setting pipeline information."""
        # Should not raise exception
        set_pipeline_info("1.0.0", "development")
        set_pipeline_info("2.1.0", "production")


class TestGetMetrics:
    """Tests for metrics export."""

    def test_get_metrics_returns_bytes(self):
        """Test that get_metrics returns bytes."""
        metrics = get_metrics()

        assert isinstance(metrics, bytes)

        if PROMETHEUS_AVAILABLE:
            # Should contain prometheus format
            assert b"#" in metrics or len(metrics) > 0
        else:
            # Should contain fallback message
            assert b"not installed" in metrics.lower()

    def test_get_metrics_format(self):
        """Test that metrics are in Prometheus format (if available)."""
        metrics = get_metrics()

        if PROMETHEUS_AVAILABLE:
            # Prometheus format uses # for comments and metric names
            text = metrics.decode("utf-8")
            # Should have some structure
            assert len(text) > 0


class TestMetricsIntegration:
    """Integration tests for metrics system."""

    def test_metrics_collector_with_batch_processing(self):
        """Test metrics collector simulating batch processing."""
        with MetricsCollector("orders") as collector:
            # Simulate processing 3 batches
            for i in range(3):
                batch_size = 100
                collector.record_batch(batch_size)

                # Simulate some failures
                collector.record_success(95)
                collector.record_failure(5)

                time.sleep(0.001)

        assert collector.records_processed == 285
        assert collector.records_failed == 15

    def test_combined_tracking(self):
        """Test using multiple tracking mechanisms together."""
        with MetricsCollector("products") as collector:
            # Track normalization
            with track_normalization("products"):
                time.sleep(0.005)

            # Track hook execution
            with track_hook_execution("pre_save", "ValidationHook"):
                time.sleep(0.002)

            # Record some data
            collector.record_success(100)

            # Record DLQ entry
            record_dlq_entry("products", "validation_error")

        assert collector.records_processed == 100


@pytest.mark.skipif(not PROMETHEUS_AVAILABLE, reason="Prometheus client not installed")
class TestPrometheusMetrics:
    """Tests that require prometheus_client to be installed."""

    def test_metrics_are_actually_recorded(self):
        """Test that metrics are actually recorded to Prometheus."""

        # Get metrics before
        metrics_before = get_metrics().decode("utf-8")

        with MetricsCollector("prom_test") as collector:
            collector.record_success(10)
            collector.record_failure(2)

        # Get metrics after
        metrics_after = get_metrics().decode("utf-8")

        # Metrics should have changed
        assert metrics_after != metrics_before
        # Should contain our table name
        assert "prom_test" in metrics_after or "atomingest" in metrics_after
