"""
Tests for streaming protocol utilities.
"""


from engine.display.streaming import (
    FrameDiff,
    MessageType,
    apply_diff,
    compress_frame,
    compute_diff,
    decode_binary_message,
    decode_diff_message,
    decode_rle,
    decompress_frame,
    encode_binary_message,
    encode_diff_message,
    encode_rle,
    should_use_diff,
)


class TestFrameDiff:
    """Tests for FrameDiff computation."""

    def test_compute_diff_all_changed(self):
        """compute_diff detects all changed lines."""
        old = ["a", "b", "c"]
        new = ["x", "y", "z"]

        diff = compute_diff(old, new)

        assert len(diff.changed_lines) == 3
        assert diff.width == 1
        assert diff.height == 3

    def test_compute_diff_no_changes(self):
        """compute_diff returns empty for identical buffers."""
        old = ["a", "b", "c"]
        new = ["a", "b", "c"]

        diff = compute_diff(old, new)

        assert len(diff.changed_lines) == 0

    def test_compute_diff_partial_changes(self):
        """compute_diff detects partial changes."""
        old = ["a", "b", "c"]
        new = ["a", "x", "c"]

        diff = compute_diff(old, new)

        assert len(diff.changed_lines) == 1
        assert diff.changed_lines[0] == (1, "x")

    def test_compute_diff_new_lines(self):
        """compute_diff detects new lines added."""
        old = ["a", "b"]
        new = ["a", "b", "c"]

        diff = compute_diff(old, new)

        assert len(diff.changed_lines) == 1
        assert diff.changed_lines[0] == (2, "c")

    def test_compute_diff_empty_old(self):
        """compute_diff handles empty old buffer."""
        old = []
        new = ["a", "b", "c"]

        diff = compute_diff(old, new)

        assert len(diff.changed_lines) == 3


class TestRLE:
    """Tests for run-length encoding."""

    def test_encode_rle_no_repeats(self):
        """encode_rle handles no repeated lines."""
        lines = [(0, "a"), (1, "b"), (2, "c")]

        encoded = encode_rle(lines)

        assert len(encoded) == 3
        assert encoded[0] == (0, "a", 1)
        assert encoded[1] == (1, "b", 1)
        assert encoded[2] == (2, "c", 1)

    def test_encode_rle_with_repeats(self):
        """encode_rle compresses repeated lines."""
        lines = [(0, "a"), (1, "a"), (2, "a"), (3, "b")]

        encoded = encode_rle(lines)

        assert len(encoded) == 2
        assert encoded[0] == (0, "a", 3)
        assert encoded[1] == (3, "b", 1)

    def test_decode_rle(self):
        """decode_rle reconstructs original lines."""
        encoded = [(0, "a", 3), (3, "b", 1)]

        decoded = decode_rle(encoded)

        assert decoded == [(0, "a"), (1, "a"), (2, "a"), (3, "b")]

    def test_encode_decode_roundtrip(self):
        """encode/decode is lossless."""
        original = [(i, f"line{i % 3}") for i in range(10)]
        encoded = encode_rle(original)
        decoded = decode_rle(encoded)

        assert decoded == original


class TestCompression:
    """Tests for frame compression."""

    def test_compress_decompress(self):
        """compress_frame is lossless."""
        buffer = [f"Line {i:02d}" for i in range(24)]

        compressed = compress_frame(buffer)
        decompressed = decompress_frame(compressed, 24)

        assert decompressed == buffer

    def test_compress_empty(self):
        """compress_frame handles empty buffer."""
        compressed = compress_frame([])
        decompressed = decompress_frame(compressed, 0)

        assert decompressed == []


class TestBinaryProtocol:
    """Tests for binary message encoding."""

    def test_encode_decode_message(self):
        """encode_binary_message is lossless."""
        payload = b"test payload"

        encoded = encode_binary_message(MessageType.FULL_FRAME, 80, 24, payload)
        msg_type, width, height, decoded_payload = decode_binary_message(encoded)

        assert msg_type == MessageType.FULL_FRAME
        assert width == 80
        assert height == 24
        assert decoded_payload == payload

    def test_encode_decode_all_types(self):
        """All message types encode correctly."""
        for msg_type in MessageType:
            payload = b"test"
            encoded = encode_binary_message(msg_type, 80, 24, payload)
            decoded_type, _, _, _ = decode_binary_message(encoded)
            assert decoded_type == msg_type


class TestDiffProtocol:
    """Tests for diff message encoding."""

    def test_encode_decode_diff(self):
        """encode_diff_message is lossless."""
        diff = FrameDiff(width=80, height=24, changed_lines=[(0, "a"), (5, "b")])

        payload = encode_diff_message(diff)
        decoded = decode_diff_message(payload)

        assert decoded == diff.changed_lines


class TestApplyDiff:
    """Tests for applying diffs."""

    def test_apply_diff(self):
        """apply_diff reconstructs new buffer."""
        old_buffer = ["a", "b", "c", "d"]
        diff = FrameDiff(width=1, height=4, changed_lines=[(1, "x"), (2, "y")])

        new_buffer = apply_diff(old_buffer, diff)

        assert new_buffer == ["a", "x", "y", "d"]

    def test_apply_diff_new_lines(self):
        """apply_diff handles new lines."""
        old_buffer = ["a", "b"]
        diff = FrameDiff(width=1, height=4, changed_lines=[(2, "c"), (3, "d")])

        new_buffer = apply_diff(old_buffer, diff)

        assert new_buffer == ["a", "b", "c", "d"]


class TestShouldUseDiff:
    """Tests for diff threshold decision."""

    def test_uses_diff_when_small_changes(self):
        """should_use_diff returns True when few changes."""
        old = ["a"] * 100
        new = ["a"] * 95 + ["b"] * 5

        assert should_use_diff(old, new, threshold=0.3) is True

    def test_uses_full_when_many_changes(self):
        """should_use_diff returns False when many changes."""
        old = ["a"] * 100
        new = ["b"] * 100

        assert should_use_diff(old, new, threshold=0.3) is False

    def test_uses_diff_at_threshold(self):
        """should_use_diff handles threshold boundary."""
        old = ["a"] * 100
        new = ["a"] * 70 + ["b"] * 30

        result = should_use_diff(old, new, threshold=0.3)
        assert result is True or result is False  # At boundary

    def test_returns_false_for_empty(self):
        """should_use_diff returns False for empty buffers."""
        assert should_use_diff([], ["a", "b"]) is False
        assert should_use_diff(["a", "b"], []) is False
