"""
Milestone 4: Observability Tests

Tests for Prometheus metrics and OpenTelemetry tracing.
"""

from unittest.mock import MagicMock

import pytest


class TestPrometheusMetrics:
    """Tests for Prometheus metrics endpoint."""

    def test_metrics_endpoint_available(self, auth_client):
        """The /metrics endpoint should be available."""
        client, _ = auth_client
        response = client.get("/metrics")
        assert response.status_code == 200
        assert "text/plain" in response.headers["content-type"]

    def test_metrics_contains_request_counter(self, auth_client):
        """Metrics should include request counter."""
        client, _ = auth_client
        # Make some requests first
        client.get("/healthz")
        client.get("/healthz")

        response = client.get("/metrics")
        content = response.text

        assert "fsal_requests_total" in content

    def test_metrics_contains_operation_counter(self, auth_client, tmp_path, monkeypatch):
        """Metrics should track filesystem operations."""
        client, token = auth_client

        # Set sandbox root
        monkeypatch.setenv("FSAL_ROOT", str(tmp_path))

        # Create a file to read
        (tmp_path / "test.txt").write_text("hello")

        # Do a read operation
        client.post(
            "/v1/fs/read",
            json={"path": "test.txt"},
            headers={"Authorization": f"Bearer {token}"},
        )

        # Check metrics
        metrics = client.get("/metrics").text
        assert "fsal_operations_total" in metrics

    def test_metrics_contains_bytes_counters(self, auth_client, tmp_path, monkeypatch):
        """Metrics should track bytes read/written."""
        client, token = auth_client
        monkeypatch.setenv("FSAL_ROOT", str(tmp_path))

        # Create and read a file
        (tmp_path / "data.txt").write_bytes(b"x" * 100)

        client.post(
            "/v1/fs/read",
            json={"path": "data.txt"},
            headers={"Authorization": f"Bearer {token}"},
        )

        metrics = client.get("/metrics").text
        assert "fsal_bytes_read_total" in metrics

    def test_metrics_contains_active_requests_gauge(self, auth_client):
        """Metrics should include active requests gauge."""
        client, _ = auth_client
        response = client.get("/metrics")
        assert "fsal_active_requests" in response.text

    def test_metrics_contains_latency_histogram(self, auth_client):
        """Metrics should include request latency histogram."""
        client, _ = auth_client
        # Make a request to generate latency data
        client.get("/healthz")

        response = client.get("/metrics")
        assert "fsal_request_duration_seconds" in response.text


class TestMetricsRecording:
    """Tests for metrics recording functions."""

    def test_record_operation(self):
        """record_operation should increment counter."""
        from aperion_fsal.service.metrics import OPERATION_COUNT, record_operation

        # Get initial value
        initial = OPERATION_COUNT.labels(operation="test_op")._value.get()

        record_operation("test_op")

        # Value should have increased
        new_value = OPERATION_COUNT.labels(operation="test_op")._value.get()
        assert new_value == initial + 1

    def test_record_bytes_read(self):
        """record_bytes_read should increment counter."""
        from aperion_fsal.service.metrics import BYTES_READ, record_bytes_read

        initial = BYTES_READ._value.get()
        record_bytes_read(1000)

        assert BYTES_READ._value.get() == initial + 1000

    def test_record_bytes_written(self):
        """record_bytes_written should increment counter."""
        from aperion_fsal.service.metrics import BYTES_WRITTEN, record_bytes_written

        initial = BYTES_WRITTEN._value.get()
        record_bytes_written(500)

        assert BYTES_WRITTEN._value.get() == initial + 500

    def test_record_sandbox_violation(self):
        """record_sandbox_violation should track violation type."""
        from aperion_fsal.service.metrics import SANDBOX_VIOLATIONS, record_sandbox_violation

        initial = SANDBOX_VIOLATIONS.labels(violation_type="traversal")._value.get()
        record_sandbox_violation("traversal")

        new_value = SANDBOX_VIOLATIONS.labels(violation_type="traversal")._value.get()
        assert new_value == initial + 1


class TestTracingModule:
    """Tests for OpenTelemetry tracing module."""

    def test_tracing_disabled_by_default(self):
        """Tracing should be disabled when env var not set."""
        from aperion_fsal.service.tracing import is_tracing_enabled

        # Should be false by default
        assert is_tracing_enabled() is False

    def test_tracing_enabled_via_env(self, monkeypatch):
        """Tracing should be enabled when FSAL_TRACING_ENABLED=true."""
        from aperion_fsal.service import tracing

        if not tracing._TRACING_AVAILABLE:
            pytest.skip("opentelemetry not installed (optional [tracing] extra)")

        monkeypatch.setenv("FSAL_TRACING_ENABLED", "true")

        # is_tracing_enabled reads env var dynamically, no reload needed
        assert tracing.is_tracing_enabled() is True

    def test_trace_operation_noop_when_disabled(self):
        """trace_operation should be a no-op when tracing is disabled."""
        from aperion_fsal.service.tracing import _NoOpSpan, trace_operation

        with trace_operation("test", {"key": "value"}) as span:
            # Should get a no-op span
            assert isinstance(span, _NoOpSpan)

            # Methods should not raise
            span.set_attribute("foo", "bar")
            span.add_event("something")

    def test_noop_span_methods(self):
        """NoOpSpan methods should do nothing without raising."""
        from aperion_fsal.service.tracing import _NoOpSpan

        span = _NoOpSpan()

        # All these should work without error
        span.set_attribute("key", "value")
        span.set_status(None)
        span.record_exception(ValueError("test"))
        span.add_event("event", {"attr": 1})


class TestMetricsMiddleware:
    """Tests for the metrics collection middleware."""

    def test_middleware_normalizes_endpoints(self):
        """Middleware should normalize endpoint paths for metric labels."""
        from aperion_fsal.service.metrics import MetricsMiddleware

        middleware = MetricsMiddleware(app=MagicMock())

        # Test normalization
        assert middleware._normalize_endpoint("/v1/fs/read") == "/v1/fs/read"
        assert middleware._normalize_endpoint("/v1/fs/write") == "/v1/fs/write"
        assert middleware._normalize_endpoint("/healthz") == "/healthz"

    def test_metrics_endpoint_not_tracked(self, auth_client):
        """The /metrics endpoint itself should not generate metrics."""
        client, _ = auth_client

        # Get metrics multiple times
        for _ in range(5):
            client.get("/metrics")

        # Check that /metrics is not in the request labels
        response = client.get("/metrics")
        lines = response.text.split("\n")

        # Find request counter lines and check none have endpoint="/metrics"
        metric_lines = [l for l in lines if "fsal_requests_total" in l and "endpoint=" in l]
        for line in metric_lines:
            assert 'endpoint="/metrics"' not in line
