"""Unit tests for Sixth Round (Re-Review) Adversarial Fixes.

Tests for 4 critical issues found in the fifth round implementation:
- Re-Review Issue #1: Metric spoofing via dictionary unpacking
- Re-Review Issue #2: Asymmetric validation in record_prediction
- Re-Review Issue #3: Inline imports in hot paths (already fixed)
- Re-Review Issue #4: Nuclear attribute stripping
"""

import pytest
from mca_sdk import MCAClient


class TestReReviewIssue1MetricSpoofing:
    """Test that users cannot spoof system metric labels (Re-Review Issue #1)."""

    def test_user_cannot_override_status_in_error(self):
        """Verify user-provided status="success" cannot override status="error"."""
        client = MCAClient(service_name="test", model_id="mdl-001", team_name="test")

        recorded_attrs = None
        original_add = client._predictions_counter.add

        def capture_add(value, attributes):
            nonlocal recorded_attrs
            recorded_attrs = attributes
            return original_add(value, attributes)

        client._predictions_counter.add = capture_add

        # User tries to spoof status
        client.record_error(
            ValueError("test error"),
            status="success",  # Malicious override attempt
        )

        # System attribute should win
        assert (
            recorded_attrs["status"] == "error"
        ), "User was able to spoof status='success' when error occurred!"

        client.shutdown()

    def test_user_cannot_override_error_type(self):
        """Verify user-provided error_type cannot override actual exception type."""
        client = MCAClient(service_name="test", model_id="mdl-001", team_name="test")

        recorded_attrs = None
        original_add = client._predictions_counter.add

        def capture_add(value, attributes):
            nonlocal recorded_attrs
            recorded_attrs = attributes
            return original_add(value, attributes)

        client._predictions_counter.add = capture_add

        # User tries to spoof error_type
        client.record_error(
            ValueError("test error"),
            error_type="FakeException",  # Malicious override attempt
        )

        # System attribute should win
        assert recorded_attrs["error_type"] == "ValueError", "User was able to override error_type!"

        client.shutdown()

    def test_user_attributes_preserved_when_not_conflicting(self):
        """Verify non-conflicting user attributes are preserved."""
        client = MCAClient(service_name="test", model_id="mdl-001", team_name="test")

        recorded_attrs = None
        original_add = client._predictions_counter.add

        def capture_add(value, attributes):
            nonlocal recorded_attrs
            recorded_attrs = attributes
            return original_add(value, attributes)

        client._predictions_counter.add = capture_add

        # User provides legitimate attributes
        client.record_error(ValueError("test error"), function="my_predict", model_version="2.0")

        # User attributes should be preserved
        assert recorded_attrs["function"] == "my_predict"
        assert recorded_attrs["model_version"] == "2.0"
        # But system attributes still enforced
        assert recorded_attrs["status"] == "error"
        assert recorded_attrs["error_type"] == "ValueError"

        client.shutdown()


class TestReReviewIssue2AsymmetricValidation:
    """Test that record_prediction validates in permissive mode (Re-Review Issue #2)."""

    def test_record_prediction_validates_in_permissive_mode(self):
        """Verify record_prediction validates attributes even in permissive mode."""
        client = MCAClient(
            service_name="test",
            model_id="mdl-001",
            team_name="test",
            strict_validation=False,  # Permissive mode
        )

        # Track if attributes were sanitized
        sanitized = False
        original_record_prediction = client.record_prediction

        def patched_record_prediction(*args, **kwargs):
            nonlocal sanitized
            # If attributes were passed and are now different, they were sanitized
            attrs_before = kwargs.get("custom_attr")
            result = original_record_prediction(*args, **kwargs)
            attrs_after = kwargs.get("custom_attr")
            if attrs_before and attrs_after and len(attrs_after) < len(attrs_before):
                sanitized = True
            return result

        # Provide attributes that exceed limits
        huge_value = "x" * 10000  # Exceeds max_value_length

        # In permissive mode, should sanitize not crash
        try:
            client.record_prediction(latency=0.1, custom_attr=huge_value)
        except Exception as e:
            pytest.fail(f"record_prediction should not crash in permissive mode: {e}")

        client.shutdown()

    def test_record_prediction_sanitizes_not_drops_in_permissive(self):
        """Verify record_prediction sanitizes invalid attrs, doesn't drop all."""
        client = MCAClient(
            service_name="test",
            model_id="mdl-001",
            team_name="test",
            strict_validation=False,
        )

        recorded_attrs = None
        original_add = client._predictions_counter.add

        def capture_add(value, attributes):
            nonlocal recorded_attrs
            recorded_attrs = attributes
            return original_add(value, attributes)

        client._predictions_counter.add = capture_add

        # Provide mix of valid and invalid attributes
        client.record_prediction(
            latency=0.1,
            valid_attr="short",
            invalid_attr="x" * 10000,  # Too long
        )

        # Valid attribute should be preserved
        assert "valid_attr" in recorded_attrs, "Valid attributes should not be dropped"
        assert recorded_attrs["valid_attr"] == "short"

        # Invalid attribute should be truncated, not missing
        assert (
            "invalid_attr" in recorded_attrs
        ), "Invalid attributes should be sanitized, not dropped"
        assert len(recorded_attrs["invalid_attr"]) <= 1024, "Invalid attribute should be truncated"

        client.shutdown()

    def test_record_prediction_raises_in_strict_mode(self):
        """Verify record_prediction still raises in strict mode."""
        client = MCAClient(
            service_name="test",
            model_id="mdl-001",
            team_name="test",
            strict_validation=True,  # Strict mode
        )

        # Provide invalid attributes
        huge_value = "x" * 10000

        with pytest.raises(Exception):
            client.record_prediction(latency=0.1, custom_attr=huge_value)

        client.shutdown()


class TestReReviewIssue3InlineImports:
    """Test that imports are at module level (Re-Review Issue #3)."""

    def test_telemetry_validator_imported_at_top(self):
        """Verify TelemetryAttributeValidator is imported at module level."""
        from mca_sdk.core import client

        # Check that TelemetryAttributeValidator is available at module level
        assert hasattr(
            client, "TelemetryAttributeValidator"
        ), "TelemetryAttributeValidator should be imported at module top"

    def test_no_inline_import_in_record_error(self):
        """Verify record_error doesn't have inline import."""
        import inspect
        from mca_sdk.core.client import MCAClient

        # Get source code of record_error method
        source = inspect.getsource(MCAClient.record_error)

        # Should not have inline import inside the method
        assert (
            "from ..validation.telemetry_attributes import" not in source
        ), "record_error should not have inline import"

    def test_no_inline_import_in_record_prediction(self):
        """Verify record_prediction doesn't have inline import."""
        import inspect
        from mca_sdk.core.client import MCAClient

        # Get source code of record_prediction method
        source = inspect.getsource(MCAClient.record_prediction)

        # Should not have inline import inside the method
        assert (
            "from ..validation.telemetry_attributes import" not in source
        ), "record_prediction should not have inline import"


class TestReReviewIssue4NuclearStripping:
    """Test that attribute sanitization is targeted, not nuclear (Re-Review Issue #4)."""

    def test_record_error_preserves_valid_attributes(self):
        """Verify record_error preserves valid attrs when one is invalid."""
        client = MCAClient(
            service_name="test",
            model_id="mdl-001",
            team_name="test",
            strict_validation=False,  # Permissive mode
        )

        recorded_attrs = None
        original_add = client._predictions_counter.add

        def capture_add(value, attributes):
            nonlocal recorded_attrs
            recorded_attrs = attributes
            return original_add(value, attributes)

        client._predictions_counter.add = capture_add

        # Provide mix of valid and one invalid attribute
        client.record_error(
            ValueError("test"),
            function="my_predict",  # Valid
            model_version="2.0",  # Valid
            trace_id="abc123",  # Valid
            invalid_field="x" * 10000,  # Invalid - too long
        )

        # Valid attributes should be preserved
        assert "function" in recorded_attrs, "Valid attributes should be preserved"
        assert recorded_attrs["function"] == "my_predict"
        assert "model_version" in recorded_attrs
        assert recorded_attrs["model_version"] == "2.0"
        assert "trace_id" in recorded_attrs
        assert recorded_attrs["trace_id"] == "abc123"

        # Invalid attribute should be truncated, not cause everything to be dropped
        assert (
            "invalid_field" in recorded_attrs
        ), "Invalid attribute should be truncated, not dropped"
        assert len(recorded_attrs["invalid_field"]) <= 1024, "Should be truncated to max length"

        # System attributes still enforced
        assert recorded_attrs["status"] == "error"
        assert recorded_attrs["error_type"] == "ValueError"

        client.shutdown()

    def test_validator_handles_sanitization(self):
        """Verify TelemetryAttributeValidator handles sanitization properly."""
        from mca_sdk.validation.telemetry_attributes import TelemetryAttributeValidator

        attrs = {"short": "value", "long": "x" * 2000}

        # In permissive mode, validator should sanitize
        sanitized = TelemetryAttributeValidator.validate(
            attrs, strict=False, max_value_length=100, max_attribute_count=10
        )

        assert "short" in sanitized, "Short values should be preserved"
        assert "long" in sanitized, "Long values should be truncated, not dropped"
        assert len(sanitized["long"]) <= 100, "Long values should be truncated"

    def test_validator_respects_count_limit(self):
        """Verify validator respects max_count."""
        from mca_sdk.validation.telemetry_attributes import TelemetryAttributeValidator

        attrs = {f"attr_{i}": f"value_{i}" for i in range(20)}

        sanitized = TelemetryAttributeValidator.validate(
            attrs, strict=False, max_value_length=1000, max_attribute_count=5
        )

        assert len(sanitized) <= 5, "Should limit to max_count attributes"

    def test_validator_handles_none_values(self):
        """Verify validator coerces None to empty string."""
        from mca_sdk.validation.telemetry_attributes import TelemetryAttributeValidator

        attrs = {"string": "value", "none": None}

        sanitized = TelemetryAttributeValidator.validate(
            attrs, strict=False, max_value_length=100, max_attribute_count=10
        )

        assert sanitized["string"] == "value"
        # Validator should coerce None to "" for OTel compatibility
        assert sanitized.get("none") == "" or "none" not in sanitized


class TestAllReReviewFixesIntegration:
    """Integration tests verifying all re-review fixes work together."""

    def test_complete_error_flow_with_spoofing_attempt(self):
        """Test complete error flow with user attempting to spoof metrics."""
        client = MCAClient(
            service_name="test",
            model_id="mdl-001",
            team_name="test",
            strict_validation=False,  # Permissive mode
        )

        recorded_attrs = None
        original_add = client._predictions_counter.add

        def capture_add(value, attributes):
            nonlocal recorded_attrs
            recorded_attrs = attributes
            return original_add(value, attributes)

        client._predictions_counter.add = capture_add

        # User provides valid attrs, one invalid attr, and tries to spoof status
        client.record_error(
            ValueError("test error"),
            function="my_predict",  # Valid
            model_version="2.0",  # Valid
            huge_field="x" * 10000,  # Invalid - should be truncated
            status="success",  # Spoofing attempt - should be overridden
            error_type="FakeException",  # Spoofing attempt - should be overridden
        )

        # Valid attributes preserved
        assert recorded_attrs["function"] == "my_predict"
        assert recorded_attrs["model_version"] == "2.0"

        # Invalid attribute truncated
        assert len(recorded_attrs["huge_field"]) <= 1024

        # Spoofing attempts blocked
        assert recorded_attrs["status"] == "error"
        assert recorded_attrs["error_type"] == "ValueError"

        client.shutdown()

    def test_complete_prediction_flow_with_invalid_attributes(self):
        """Test complete prediction flow with invalid attributes in permissive mode."""
        client = MCAClient(
            service_name="test",
            model_id="mdl-001",
            team_name="test",
            strict_validation=False,
        )

        recorded_attrs = None
        original_add = client._predictions_counter.add

        def capture_add(value, attributes):
            nonlocal recorded_attrs
            recorded_attrs = attributes
            return original_add(value, attributes)

        client._predictions_counter.add = capture_add

        # Provide mix of valid and invalid
        client.record_prediction(latency=0.1, valid_attr="value", invalid_attr="x" * 10000)

        # Valid attributes preserved
        assert recorded_attrs["valid_attr"] == "value"

        # Invalid attributes truncated
        assert len(recorded_attrs["invalid_attr"]) <= 1024

        client.shutdown()
