"""
Example: Adversarial tests for WebSocket schemas.

This file demonstrates how to use the adversarial testing framework
to find bugs in Pydantic schemas.

Run with:
    pytest lightwave/schema/testing/examples/test_websocket_adversarial.py -v
"""

import pytest
from pydantic import ValidationError

from lightwave.schema.pydantic.contracts.websockets import (
    ChatMessageInbound,
    NotificationNewOutbound,
    PresenceUpdateInbound,
    SyncSubscribeInbound,
    WSErrorMessage,
)
from lightwave.schema.testing import (
    RejectionTestSuite,
    SchemaTester,
    WebSocketSpecChecker,
    adversarial_test,
    assert_websocket_spec_aligned,
    rejection_test,
)

# =============================================================================
# Example 1: Using SchemaTester (comprehensive, automated)
# =============================================================================


class TestChatMessageAdversarial:
    """Adversarial tests for ChatMessageInbound using SchemaTester."""

    def test_full_adversarial_suite(self) -> None:
        """Run comprehensive adversarial tests."""
        tester = SchemaTester(
            ChatMessageInbound,
            valid_instance={
                "content": "Hello, how are you?",
                "conversation_id": "conv-123",
            },
            invalid_values={
                "content": [None],  # Empty string is a known bug!
                "conversation_id": ["", None],
            },
        )
        report = tester.run_all()

        # Print detailed report for debugging
        print(f"\n{report.summary()}")

        # For now, we know there's a bug with empty content
        # In real code, you'd assert: assert report.bugs_found == 0
        assert report.total > 0, "Should have run tests"


# =============================================================================
# Example 2: Using rejection_test decorator
# =============================================================================


class TestPresenceRejection:
    """Rejection tests using decorators."""

    @rejection_test()
    def test_invalid_status_rejected(self) -> None:
        """Test that invalid status values are rejected."""
        PresenceUpdateInbound(status="INVALID_STATUS")

    @rejection_test()
    def test_none_status_rejected(self) -> None:
        """Test that None status is rejected."""
        PresenceUpdateInbound(status=None)  # type: ignore

    @rejection_test(message_contains="status")
    def test_error_mentions_field(self) -> None:
        """Test that error message mentions the problematic field."""
        PresenceUpdateInbound(status="bad")


# =============================================================================
# Example 3: Using adversarial_test decorator
# =============================================================================


class TestNotificationAdversarial:
    """Adversarial tests using the adversarial_test decorator."""

    @adversarial_test(expects_failure=True)
    def test_missing_required_fields(self) -> None:
        """Test that missing required fields raise ValidationError."""
        NotificationNewOutbound()  # Missing id, notification_type, title

    @adversarial_test(expects_failure=True)
    def test_invalid_notification_type(self) -> None:
        """Test that invalid notification type is rejected."""
        NotificationNewOutbound(
            id="test-123",
            notification_type="INVALID_TYPE",
            title="Test",
        )

    @adversarial_test(expects_failure=True)
    def test_invalid_priority(self) -> None:
        """Test that invalid priority is rejected."""
        NotificationNewOutbound(
            id="test-123",
            notification_type="info",
            title="Test",
            priority="INVALID_PRIORITY",
        )

    @adversarial_test(expects_failure=False)
    def test_valid_notification_accepted(self) -> None:
        """Test that valid notification is accepted."""
        notif = NotificationNewOutbound(
            id="test-123",
            notification_type="info",
            title="Test Notification",
            priority="normal",
        )
        assert notif.id == "test-123"


# =============================================================================
# Example 4: RejectionTestSuite base class
# =============================================================================


class TestSyncSubscribeRejection(RejectionTestSuite[SyncSubscribeInbound]):
    """Rejection test suite for SyncSubscribeInbound."""

    schema_class = SyncSubscribeInbound
    required_fields = ["entity_types"]
    invalid_values = {
        "entity_types": [[], None],  # Must have at least one entity type
    }
    valid_construction_kwargs = {
        "entity_types": ["CallSheet", "ShotList"],
    }


# =============================================================================
# Example 5: Spec Alignment Testing
# =============================================================================


class TestWebSocketSpecAlignment:
    """Test that Pydantic schemas align with YAML spec."""

    def test_spec_alignment_report(self) -> None:
        """Generate and review spec alignment report."""
        try:
            checker = WebSocketSpecChecker()
            report = checker.check_all()
        except ImportError as e:
            pytest.skip(f"WebSocket module not yet implemented: {e}")

        print(f"\n{report.summary()}")

        # Count issues by category
        missing = report.missing_count
        orphan = report.orphan_count

        print(f"\nMissing (YAML has, Pydantic doesn't): {missing}")
        print(f"Orphan (Pydantic has, YAML doesn't): {orphan}")

        # In real code, you might want strict alignment:
        # assert report.is_aligned, report.summary()

    @pytest.mark.skip(reason="Enable when YAML spec is complete")
    def test_strict_alignment(self) -> None:
        """Strict test that all specs are aligned."""
        assert_websocket_spec_aligned()


# =============================================================================
# Example 6: Manual Boundary Testing
# =============================================================================


class TestChatMessageBoundaries:
    """Manual boundary tests for ChatMessageInbound."""

    def test_content_at_max_length(self) -> None:
        """Test content at exactly max length (10000)."""
        msg = ChatMessageInbound(
            content="x" * 10000,
            conversation_id="test",
        )
        assert len(msg.content) == 10000

    def test_content_beyond_max_length_rejected(self) -> None:
        """Test content beyond max length is rejected."""
        with pytest.raises(ValidationError):
            ChatMessageInbound(
                content="x" * 10001,
                conversation_id="test",
            )

    @pytest.mark.xfail(reason="Known bug: empty content accepted")
    def test_empty_content_rejected(self) -> None:
        """Test that empty content is rejected (KNOWN BUG)."""
        with pytest.raises(ValidationError):
            ChatMessageInbound(
                content="",
                conversation_id="test",
            )


# =============================================================================
# Example 7: Error Code Validation
# =============================================================================


class TestWSErrorMessageRejection:
    """Test WSErrorMessage rejects invalid error codes."""

    @rejection_test()
    def test_invalid_error_code_rejected(self) -> None:
        """Test that made-up error codes are rejected."""
        WSErrorMessage(
            error_code="FAKE_ERROR_CODE",
            error_message="This should fail",
        )

    def test_valid_error_codes_accepted(self) -> None:
        """Test that valid error codes are accepted."""
        valid_codes = [
            "WS_AUTH_FAILED",
            "WS_RATE_LIMITED",
            "WS_INVALID_MESSAGE",
        ]
        for code in valid_codes:
            msg = WSErrorMessage(
                error_code=code,  # type: ignore
                error_message=f"Error: {code}",
            )
            assert msg.error_code == code
