"""E2E tests for log flow: SDK → Collector → GCP Cloud Logging.

Tests verify that structured logs recorded via SDK successfully propagate
through the OTel Collector and appear in GCP Cloud Logging with proper
structure and NO PHI leakage.
"""

import time
import re
import pytest
from mca_sdk import MCAClient


@pytest.mark.integration
@pytest.mark.error_scenario  # Collector logs are expected
class TestLogsE2E:
    """End-to-end tests for log propagation and audit trail."""

    def test_prediction_log_propagates_to_gcp(
        self,
        docker_compose_environment,
        gcp_logging_client,
        gcp_project_id,
        wait_for_telemetry,
        cleanup_collector_exports,
    ):
        """Test AC4: Structured prediction log flows to GCP Cloud Logging.

        Given: SDK records prediction event
        When: Log propagates through collector
        Then: Structured log with prediction details is retrievable from Cloud Logging
        """
        # Arrange
        client = MCAClient(
            service_name="e2e-log-test",
            model_id="e2e-log-mdl-001",
            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 prediction (generates log)
        prediction_id = "e2e-log-pred-001"
        client.record_prediction(
            latency=0.089,
            prediction_id=prediction_id,
            model_version="2.0.0",
            prediction_class="positive",
            confidence=0.89,
        )

        client.shutdown()
        wait_for_telemetry(10, "log propagation to file exporter")

        # Assert: Query Cloud Logging
        end_time = time.time()
        logs = gcp_logging_client.query_logs(
            project_id=gcp_project_id,
            filter_str=f'resource.labels.prediction_id="{prediction_id}"',
            start_time=start_time,
            end_time=end_time,
        )

        # Verify logs exist
        assert len(logs) > 0, f"No logs found. Checked time window: {start_time} to {end_time}"

        # Find log entry related to our prediction
        prediction_log = None
        for log in logs:
            # Check if prediction_id is in attributes or message
            if prediction_id in str(log.get("attributes", {})) or prediction_id in log.get(
                "message", ""
            ):
                prediction_log = log
                break

        assert (
            prediction_log is not None
        ), f"Log with prediction_id={prediction_id} not found. Available logs: {logs}"

        # Verify log structure
        assert "severity" in prediction_log, "Log missing severity field"
        assert prediction_log["severity"] in [
            "INFO",
            "NOTICE",
            "WARNING",
            "ERROR",
        ], f"Unexpected severity: {prediction_log['severity']}"

        # Verify prediction metadata is present
        log_content = str(prediction_log)
        assert prediction_id in log_content, "prediction_id not in log content"

    def test_no_phi_in_logs(
        self,
        docker_compose_environment,
        gcp_logging_client,
        gcp_project_id,
        wait_for_telemetry,
        cleanup_collector_exports,
    ):
        """Test AC4 (PHI verification): Logs contain no PHI data.

        Given: Application sanitizes PHI before SDK calls
        When: Logs are queried from Cloud Logging
        Then: No PHI patterns detected AND sanitized values ARE present

        Note: SDK does NOT automatically mask PHI - application must sanitize.
        This test verifies proper sanitization workflow.
        """
        # Arrange
        client = MCAClient(
            service_name="e2e-phi-test",
            model_id="e2e-phi-mdl-001",
            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 prediction with sanitized data (no PHI)
        # Application MUST sanitize before calling SDK
        sanitized_patient_id = "HASHED_abc123"  # Already hashed by app
        prediction_id = "e2e-phi-pred-001"

        client.record_prediction(
            latency=0.1,
            prediction_id=prediction_id,
            patient_id=sanitized_patient_id,  # Pre-sanitized
            department="oncology",
        )

        client.shutdown()
        wait_for_telemetry(10, "PHI validation logs")

        # Assert: Verify NO PHI patterns in logs
        end_time = time.time()
        logs = gcp_logging_client.query_logs(
            project_id=gcp_project_id,
            filter_str='severity>="INFO"',
            start_time=start_time,
            end_time=end_time,
        )

        # Critical: logs must exist for valid test
        assert len(logs) > 0, "No logs found - cannot validate PHI absence. Test is invalid."

        # ENHANCED: Verify sanitized value IS present (proves logs captured our data)
        log_content_all = " ".join([str(log) for log in logs])
        assert sanitized_patient_id in log_content_all, (
            f"Sanitized patient_id '{sanitized_patient_id}' not found in logs. "
            f"This suggests logs weren't captured, making PHI test invalid."
        )

        # Define PHI patterns to check for
        phi_patterns = [
            (r"\d{3}-\d{2}-\d{4}", "SSN format"),  # SSN format
            (r"\b\d{2}/\d{2}/\d{4}\b", "DOB format"),  # DOB format
            (r"\bMRN\d{6,}\b", "Medical Record Number"),  # Medical Record Number
            (r"\bpatient_name\b", "Patient name field"),  # Direct name fields
            (
                r"\b[A-Z][a-z]+ [A-Z][a-z]+\b.*\b(patient|member)\b",
                "Name + patient context",
            ),
        ]

        phi_violations = []
        for log in logs:
            log_text = str(log)
            for pattern, description in phi_patterns:
                matches = re.findall(pattern, log_text, re.IGNORECASE)
                if matches:
                    # Exclude our sanitized value from violations
                    real_matches = [m for m in matches if m != sanitized_patient_id]
                    if real_matches:
                        phi_violations.append(
                            {
                                "pattern": description,
                                "matches": real_matches,
                                "log_excerpt": log_text[:200],
                            }
                        )

        assert len(phi_violations) == 0, "PHI patterns detected in logs:\n" + "\n".join(
            [f"  - {v['pattern']}: {v['matches']}" for v in phi_violations]
        )

    def test_log_attributes_match_prediction(
        self,
        docker_compose_environment,
        gcp_logging_client,
        gcp_project_id,
        wait_for_telemetry,
        cleanup_collector_exports,
    ):
        """Test that log attributes match prediction metadata exactly.

        Given: SDK records prediction with specific attributes
        When: Logs are retrieved from Cloud Logging
        Then: Log attributes match prediction attributes with correct types
        """
        # Arrange
        client = MCAClient(
            service_name="e2e-log-attrs",
            model_id="e2e-log-mdl-002",
            team_name="e2e-test-team",
            model_category="generative",
            model_type="llm",
            otel_endpoint=docker_compose_environment["collector_otlp"],
            otel_protocol="http",
        )

        start_time = time.time()

        # Act: Record with specific test attributes
        test_attrs = {
            "prediction_id": "e2e-log-pred-002",
            "model_version": "3.0.0",
            "inference_engine": "transformers",
            "gpu_used": "true",
            "batch_size": "1",
        }

        client.record_prediction(latency=0.5, **test_attrs)

        client.shutdown()
        wait_for_telemetry(10, "attribute synchronization")

        # Assert
        end_time = time.time()
        logs = gcp_logging_client.query_logs(
            project_id=gcp_project_id,
            filter_str=f'resource.labels.prediction_id="{test_attrs["prediction_id"]}"',
            start_time=start_time,
            end_time=end_time,
        )

        assert len(logs) > 0, "No logs found for attribute matching test"

        # Find our specific log
        our_log = None
        for log in logs:
            log_str = str(log)
            if test_attrs["prediction_id"] in log_str:
                our_log = log
                break

        assert (
            our_log is not None
        ), f"Log with prediction_id={test_attrs['prediction_id']} not found"

        # Verify attributes are present (may be in attributes dict or message)
        log_content = str(our_log)
        log_attrs = our_log.get("attributes", {})

        # Check each attribute is present somewhere in the log
        for key, expected_value in test_attrs.items():
            # Check in attributes dict or in full log content
            found = False

            # Check attributes dict (with various key formats)
            for attr_key in [key, key.replace("_", "."), key.replace("_", "-")]:
                if attr_key in log_attrs:
                    actual_value = str(log_attrs[attr_key])
                    if actual_value == expected_value:
                        found = True
                        break

            # Check in full log content as fallback
            if not found and expected_value in log_content:
                found = True

            assert found, (
                f"Attribute {key}={expected_value} not found in log. "
                f"Attributes: {log_attrs}, Content excerpt: {log_content[:300]}"
            )
