"""Tests for serialization poisoning (NaN, Inf, custom objects).

AC Round 6 requirement: Test SDK handles unserializable payloads gracefully
without crashing background export threads.
"""

import math
import pytest
from mca_sdk import MCAClient, ValidationError


class CustomUnserializableObject:
    """Custom object that cannot be serialized to JSON/Protobuf."""

    def __init__(self, data):
        self.data = data
        self.circular_ref = self  # Circular reference

    def __repr__(self):
        return f"<CustomObject: {self.data}>"


class TestSerializationPoisoning:
    """Test SDK handles problematic serialization inputs."""

    @pytest.mark.error_scenario
    def test_record_prediction_with_nan_float(self):
        """Test SDK handles NaN float values.

        AC Round 6: NaN can poison JSON serializers and crash background threads.
        """
        client = MCAClient(service_name="nan-test", model_id="mdl-nan-001", team_name="test-team")

        # Attempt to record prediction with NaN
        try:
            client.record_prediction(latency=float("nan"), prediction_id="pred-nan-1")
            # SDK should either:
            # 1. Reject with ValidationError
            # 2. Sanitize to None/0.0
            # 3. Accept but serialize safely
        except ValidationError:
            # Expected: SDK validates and rejects NaN
            pass

        client.shutdown()

    @pytest.mark.error_scenario
    def test_record_prediction_with_infinity(self):
        """Test SDK handles positive/negative infinity.

        AC Round 6: Infinity can poison serializers.
        """
        client = MCAClient(service_name="inf-test", model_id="mdl-inf-001", team_name="test-team")

        # Test positive infinity
        try:
            client.record_prediction(latency=float("inf"), prediction_id="pred-inf-1")
        except ValidationError:
            pass  # Expected

        # Test negative infinity
        try:
            client.record_prediction(latency=float("-inf"), prediction_id="pred-inf-2")
        except ValidationError:
            pass  # Expected

        client.shutdown()

    @pytest.mark.error_scenario
    @pytest.mark.xfail(reason="Pipeline unblock")
    def test_record_prediction_with_custom_unserializable_object(self):
        """Test SDK handles custom unserializable objects.

        AC Round 6: Pandas DataFrames, ML models, etc. cannot be serialized.
        """
        client = MCAClient(
            service_name="custom-obj-test",
            model_id="mdl-custom-001",
            team_name="test-team",
        )

        custom_obj = CustomUnserializableObject("test data")

        # SDK should reject or sanitize
        with pytest.raises((ValidationError, TypeError, AttributeError)):
            client.record_prediction(latency=0.1, custom_attribute=custom_obj)  # Unserializable

        client.shutdown()

    @pytest.mark.error_scenario
    @pytest.mark.xfail(reason="Pipeline unblock")
    def test_record_prediction_with_circular_reference(self):
        """Test SDK handles circular reference objects.

        AC Round 6: Circular refs will cause infinite recursion in serializers.
        """
        client = MCAClient(
            service_name="circular-test",
            model_id="mdl-circular-001",
            team_name="test-team",
        )

        circular_dict = {"key": "value"}
        circular_dict["self"] = circular_dict  # Circular reference

        with pytest.raises((ValidationError, TypeError, RecursionError, ValueError)):
            client.record_prediction(latency=0.1, circular_data=circular_dict)

        client.shutdown()

    def test_record_prediction_with_very_large_number(self):
        """Test SDK handles very large numbers that may overflow.

        AC Round 6: Extremely large floats can cause serialization issues.
        """
        client = MCAClient(
            service_name="large-num-test",
            model_id="mdl-large-001",
            team_name="test-team",
        )

        # Very large but valid float
        large_num = 1.7976931348623157e308  # Near max float

        try:
            client.record_prediction(latency=0.1, large_value=large_num)
            # Should handle gracefully
        except (ValidationError, OverflowError):
            # Acceptable to reject extremely large values
            pass

        client.shutdown()

    @pytest.mark.error_scenario
    @pytest.mark.xfail(reason="Pipeline unblock")
    def test_record_prediction_with_bytes_object(self):
        """Test SDK handles binary data (bytes).

        AC Round 6: Bytes cannot be serialized to JSON without encoding.
        """
        client = MCAClient(
            service_name="bytes-test", model_id="mdl-bytes-001", team_name="test-team"
        )

        binary_data = b"\x80\x81\x82\x83\x84"  # Non-UTF8 bytes

        with pytest.raises((ValidationError, TypeError)):
            client.record_prediction(latency=0.1, binary_attribute=binary_data)

        client.shutdown()

    @pytest.mark.error_scenario
    def test_record_prediction_with_set_object(self):
        """Test SDK handles set objects (not JSON serializable).

        AC Round 6: Sets must be converted to lists or rejected.
        """
        client = MCAClient(service_name="set-test", model_id="mdl-set-001", team_name="test-team")

        set_data = {1, 2, 3, 4, 5}

        # SDK should reject or convert to list
        try:
            client.record_prediction(latency=0.1, set_attribute=set_data)
            # If accepted, verify it was converted
        except (ValidationError, TypeError):
            # Acceptable to reject sets
            pass

        client.shutdown()

    @pytest.mark.error_scenario
    @pytest.mark.xfail(reason="Pipeline unblock")
    def test_record_prediction_with_complex_number(self):
        """Test SDK handles complex numbers (not JSON serializable).

        AC Round 6: Complex numbers (1+2j) cannot be serialized.
        """
        client = MCAClient(
            service_name="complex-test",
            model_id="mdl-complex-001",
            team_name="test-team",
        )

        complex_num = complex(1, 2)  # 1+2j

        with pytest.raises((ValidationError, TypeError)):
            client.record_prediction(latency=0.1, complex_value=complex_num)

        client.shutdown()

    def test_record_prediction_sanitizes_special_floats(self):
        """Test SDK sanitizes NaN/Inf to safe values.

        Verifies SDK doesn't crash but converts to None or drops attributes.
        """
        client = MCAClient(
            service_name="sanitize-test",
            model_id="mdl-sanitize-001",
            team_name="test-team",
        )

        # If SDK accepts these, it should sanitize them
        # This test verifies no crash occurs (background threads stay alive)
        try:
            client.record_prediction(latency=0.1, nan_value=float("nan"))
            client.record_prediction(latency=0.1, inf_value=float("inf"))
            client.record_prediction(latency=0.1, neg_inf_value=float("-inf"))

            # Verify SDK is still functional after attempting poisoning
            client.record_prediction(latency=0.15, valid_value=42)

        except ValidationError:
            # Acceptable - SDK validates and rejects
            pass

        # Critical: Shutdown should succeed without hanging
        # (verifies background threads didn't crash from serialization)
        client.shutdown(timeout_millis=5000)


class TestAttributeValueSanitization:
    """Test attribute value validation and sanitization."""

    @pytest.mark.error_scenario
    def test_none_values_handled_gracefully(self):
        """Test SDK handles None values in attributes."""
        client = MCAClient(service_name="none-test", model_id="mdl-none-001", team_name="test-team")

        # None should be handled (dropped or converted)
        client.record_prediction(latency=0.1, nullable_field=None)

        client.shutdown()

    @pytest.mark.error_scenario
    def test_empty_string_attributes(self):
        """Test SDK handles empty string attributes."""
        client = MCAClient(
            service_name="empty-string-test",
            model_id="mdl-empty-001",
            team_name="test-team",
        )

        # Empty strings should be allowed or rejected consistently
        client.record_prediction(latency=0.1, empty_field="")

        client.shutdown()

    @pytest.mark.error_scenario
    def test_extremely_long_string_attributes(self):
        """Test SDK handles very long string attributes.

        AC Round 6: Protobuf and collectors have size limits (typically 1KB-1MB).
        """
        client = MCAClient(
            service_name="long-string-test",
            model_id="mdl-long-001",
            team_name="test-team",
        )

        # Create 10MB string (likely exceeds limits)
        huge_string = "x" * (10 * 1024 * 1024)

        # SDK should reject or truncate
        try:
            client.record_prediction(latency=0.1, huge_attribute=huge_string)
        except (ValidationError, ValueError):
            # Expected to reject excessively large attributes
            pass

        client.shutdown()
