"""
Tests for analytics functionality (logging, Prometheus, OTLP telemetry)
"""

import pytest
import os
import json
from unittest.mock import patch, MagicMock, call
from flask import g
from app import create_app, log_event, track_event


@pytest.fixture
def app():
    """Create test Flask application and initialize DB tables."""
    app = create_app({"TESTING": True, "SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:"})
    from app import db

    with app.app_context():
        db.create_all()
    try:
        yield app
    finally:
        try:
            with app.app_context():
                db.drop_all()
        except Exception:
            pass


@pytest.fixture
def client(app):
    """Create test client"""
    return app.test_client()


class TestLogEvent:
    """Tests for structured JSON logging"""

    def test_log_event_basic(self, app):
        """Test basic log event"""
        with app.app_context():
            with app.test_request_context():
                g.request_id = "test-request-123"
                # This should not raise an exception
                log_event("test.event", user_id=1, test_data="value")

    def test_log_event_without_request_context(self, app):
        """Test that log_event handles missing request context gracefully"""
        with app.app_context():
            # Should not raise an exception even without request context
            log_event("test.event", user_id=1)

    def test_log_event_with_extra_data(self, app):
        """Test log event with various data types"""
        with app.app_context():
            with app.test_request_context():
                g.request_id = "test-request-456"
                log_event("test.event", user_id=1, project_id=42, duration=3600, success=True, tags=["tag1", "tag2"])


class TestTrackEvent:
    """Tests for event tracking"""

    @patch("app.telemetry.service.is_detailed_analytics_enabled", return_value=True)
    @patch("app.telemetry.service._send_otlp_event")
    def test_track_event_when_enabled(self, mock_send, _mock_enabled, app):
        """Test that events are sent when OTLP is configured"""
        with patch.dict(os.environ, {"OTEL_EXPORTER_OTLP_ENDPOINT": "https://otlp.example.com", "OTEL_EXPORTER_OTLP_TOKEN": "x"}):
            track_event(123, "test.event", {"property": "value"})
            assert mock_send.called
            call_args = mock_send.call_args
            assert call_args[1]["identity"] == "123"
            assert call_args[1]["event_name"] == "test.event"
            assert call_args[1]["properties"]["property"] == "value"

    @patch("app.telemetry.service.is_detailed_analytics_enabled", return_value=False)
    @patch("app.telemetry.service._send_otlp_event")
    def test_track_event_when_disabled(self, mock_send, _mock_enabled, app):
        """Test that events are not sent when sink is not configured"""
        with patch.dict(os.environ, {"OTEL_EXPORTER_OTLP_ENDPOINT": "", "OTEL_EXPORTER_OTLP_TOKEN": ""}):
            track_event(123, "test.event", {"property": "value"})
            mock_send.assert_not_called()

    @patch("app.telemetry.service.is_detailed_analytics_enabled", return_value=True)
    @patch("app.telemetry.service._send_otlp_event")
    def test_track_event_handles_errors_gracefully(self, mock_send, _mock_enabled, app):
        """Test that tracking errors don't crash the application"""
        mock_send.side_effect = Exception("Telemetry error")
        with patch.dict(os.environ, {"OTEL_EXPORTER_OTLP_ENDPOINT": "https://otlp.example.com", "OTEL_EXPORTER_OTLP_TOKEN": "x"}):
            # Should not raise an exception
            track_event(123, "test.event", {})

    def test_track_event_with_none_properties(self, app):
        """Test that track_event handles None properties"""
        with patch.dict(os.environ, {"OTEL_EXPORTER_OTLP_ENDPOINT": "https://otlp.example.com", "OTEL_EXPORTER_OTLP_TOKEN": "x"}):
            with patch("app.telemetry.service.is_detailed_analytics_enabled", return_value=True):
                with patch("app.telemetry.service._send_otlp_event") as mock_send:
                    track_event(123, "test.event", None)
                    call_args = mock_send.call_args
                    assert isinstance(call_args[1]["properties"], dict)
                    assert "environment" in call_args[1]["properties"]


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

    def test_metrics_endpoint_exists(self, client):
        """Test that /metrics endpoint exists"""
        response = client.get("/metrics")
        assert response.status_code == 200
        assert response.content_type == "text/plain; version=0.0.4; charset=utf-8"

    def test_metrics_endpoint_format(self, client):
        """Test that /metrics returns Prometheus format"""
        response = client.get("/metrics")
        data = response.data.decode("utf-8")

        # Should contain our custom metrics
        assert "tt_requests_total" in data
        assert "tt_request_latency_seconds" in data

    def test_metrics_are_incremented(self, client):
        """Test that metrics are incremented on requests"""
        # Make a request to trigger metric recording
        response = client.get("/metrics")
        assert response.status_code == 200

        # Get metrics
        response = client.get("/metrics")
        data = response.data.decode("utf-8")

        # Should have recorded requests
        assert "tt_requests_total" in data


class TestAnalyticsIntegration:
    """Integration tests for analytics in routes"""

    @patch("app.routes.auth.log_event")
    @patch("app.routes.auth.track_event")
    def test_login_analytics(self, mock_track, mock_log, authenticated_client):
        """Test that login events are tracked"""
        # Use authenticated client to verify analytics are initialized
        # The actual login tracking is tested via the mocks being available
        response = authenticated_client.get("/dashboard")

        # Verify response is successful (analytics don't break the app)
        assert response.status_code == 200

        # Note: This test primarily verifies analytics hooks don't break the login flow

    @patch("app.routes.timer.log_event")
    @patch("app.routes.timer.track_event")
    def test_timer_analytics_integration(self, mock_track, mock_log, app, client):
        """Test that timer events are tracked (integration test placeholder)"""
        # This is a placeholder - actual implementation would require:
        # 1. Authenticated session
        # 2. Valid project
        # 3. Timer start/stop operations
        pass


class TestSentryIntegration:
    """Tests for Sentry error monitoring"""

    @patch("app.sentry_sdk.init")
    def test_sentry_initializes_when_dsn_set(self, mock_init):
        """Test that Sentry initializes when DSN is provided"""
        with patch.dict(
            os.environ,
            {"SENTRY_DSN": "https://test@sentry.io/123", "SENTRY_TRACES_RATE": "0.1", "FLASK_ENV": "production"},
        ):
            app = create_app({"TESTING": True})
            # Sentry should have been initialized
            # Note: The actual initialization happens in create_app

    def test_sentry_not_initialized_without_dsn(self):
        """Test that Sentry is not initialized when DSN is not set"""
        with patch.dict(os.environ, {"SENTRY_DSN": ""}, clear=True):
            with patch("app.sentry_sdk.init") as mock_init:
                app = create_app({"TESTING": True})
                # Sentry init should not be called
                mock_init.assert_not_called()


class TestRequestIDAttachment:
    """Tests for request ID attachment"""

    def test_request_id_attached(self, app, client):
        """Test that request ID is attached to requests"""
        with app.app_context():
            with app.test_request_context():
                # Trigger the before_request hook
                with client:
                    response = client.get("/metrics")
                    # Request ID should be set in g
                    # Note: This test might need adjustment based on context handling


class TestAnalyticsEventSchema:
    """Tests to ensure analytics events follow the documented schema"""

    def test_event_naming_convention(self):
        """Test that event names follow resource.action pattern"""
        valid_events = [
            "auth.login",
            "auth.logout",
            "timer.started",
            "timer.stopped",
            "project.created",
            "project.updated",
            "export.csv",
            "report.viewed",
        ]

        for event_name in valid_events:
            parts = event_name.split(".")
            assert len(parts) == 2, f"Event {event_name} should follow resource.action pattern"
            assert parts[0].isalpha(), f"Resource part should be alphabetic: {event_name}"
            assert parts[1].replace("_", "").isalpha(), f"Action part should be alphabetic: {event_name}"


class TestAnalyticsPrivacy:
    """Tests to ensure analytics respect privacy guidelines"""

    def test_no_pii_in_standard_events(self, app):
        """Test that standard events don't include PII"""
        # Events should use IDs, not emails or usernames
        with app.app_context():
            with app.test_request_context():
                g.request_id = "test-123"

                # This is acceptable (uses ID)
                log_event("test.event", user_id=123)

                # In production, events should NOT include:
                # - email addresses
                # - usernames (use IDs instead)
                # - IP addresses (unless explicitly needed)
                # - passwords or tokens

    @patch("app.telemetry.service.is_detailed_analytics_enabled", return_value=True)
    @patch("app.telemetry.service._send_otlp_event")
    def test_telemetry_uses_internal_ids(self, mock_send, _mock_enabled, app):
        """Test that telemetry events use internal IDs, not PII"""
        with patch.dict(os.environ, {"OTEL_EXPORTER_OTLP_ENDPOINT": "https://otlp.example.com", "OTEL_EXPORTER_OTLP_TOKEN": "x"}):
            # Should use numeric ID, not email
            track_event(123, "test.event", {"project_id": 456})

            call_args = mock_send.call_args
            assert call_args[1]["identity"] == "123"


class TestAnalyticsPerformance:
    """Tests to ensure analytics don't impact performance"""

    def test_analytics_dont_block_requests(self, client):
        """Test that analytics operations don't significantly delay requests"""
        import time

        start = time.time()
        response = client.get("/metrics")
        duration = time.time() - start

        # Request should complete quickly even with analytics
        assert duration < 1.0  # Should complete in less than 1 second
        assert response.status_code == 200

    @patch("app.telemetry.service._send_otlp_event")
    def test_analytics_errors_dont_break_app(self, mock_send, app, client):
        """Test that analytics failures don't break the application"""
        mock_send.side_effect = Exception("Analytics service down")

        # Application should still work
        response = client.get("/metrics")
        assert response.status_code == 200
