import struct

import pytest

from skytale_sdk.envelope import Envelope, Protocol


class TestProtocolEnum:
    def test_all_protocols_exist(self):
        expected = {"raw", "a2a", "mcp", "slim", "acp", "anp", "lmos", "nlip"}
        assert {p.value for p in Protocol} == expected

    def test_protocol_is_str_enum(self):
        assert isinstance(Protocol.A2A, str)
        assert Protocol.A2A == "a2a"


class TestSerializeDeserialize:
    def test_roundtrip_basic(self):
        env = Envelope(Protocol.A2A, "application/json", b'{"parts":[]}')
        assert Envelope.deserialize(env.serialize()) == env

    def test_roundtrip_with_metadata(self):
        env = Envelope(
            Protocol.MCP,
            "application/json",
            b'{"method":"tools/list"}',
            metadata={"version": 2, "routing": "broadcast"},
        )
        restored = Envelope.deserialize(env.serialize())
        assert restored == env

    def test_roundtrip_empty_payload(self):
        env = Envelope(Protocol.RAW, "text/plain", b"")
        restored = Envelope.deserialize(env.serialize())
        assert restored.payload == b""
        assert restored.protocol == Protocol.RAW

    def test_roundtrip_binary_payload(self):
        payload = bytes(range(256))
        env = Envelope(Protocol.SLIM, "application/octet-stream", payload)
        restored = Envelope.deserialize(env.serialize())
        assert restored.payload == payload

    def test_all_protocols_roundtrip(self):
        for proto in Protocol:
            env = Envelope(proto, "text/plain", b"test")
            restored = Envelope.deserialize(env.serialize())
            assert restored.protocol == proto

    def test_metadata_none_omitted_from_header(self):
        env = Envelope(Protocol.RAW, "text/plain", b"hello", metadata=None)
        data = env.serialize()
        # Extract the header JSON
        header_len = struct.unpack("<I", data[:4])[0]
        import json
        header = json.loads(data[4:4 + header_len])
        assert "md" not in header


class TestWireFormat:
    def test_starts_with_4_byte_le_header_length(self):
        env = Envelope(Protocol.RAW, "text/plain", b"hello")
        data = env.serialize()
        header_len = struct.unpack("<I", data[:4])[0]
        # The header JSON should be at least a few bytes
        assert header_len > 0
        assert header_len < len(data) - 4

    def test_payload_at_end(self):
        payload = b"my-payload-data"
        env = Envelope(Protocol.RAW, "text/plain", payload)
        data = env.serialize()
        assert data.endswith(payload)

    def test_header_is_compact_json(self):
        env = Envelope(Protocol.A2A, "application/json", b"x")
        data = env.serialize()
        header_len = struct.unpack("<I", data[:4])[0]
        header_raw = data[4:4 + header_len]
        # Compact JSON has no spaces
        assert b" " not in header_raw


class TestDeserializeErrors:
    def test_too_short(self):
        with pytest.raises(ValueError, match="too short"):
            Envelope.deserialize(b"\x00\x00")

    def test_header_exceeds_max(self):
        # Craft a header length larger than 65536
        bad = struct.pack("<I", 100000) + b"\x00" * 100000
        with pytest.raises(ValueError, match="exceeds maximum"):
            Envelope.deserialize(bad)

    def test_header_exceeds_data(self):
        # Header length says 100 but only 10 bytes of data
        bad = struct.pack("<I", 100) + b"\x00" * 10
        with pytest.raises(ValueError, match="exceeds available"):
            Envelope.deserialize(bad)

    def test_malformed_json(self):
        # Valid header length but not valid JSON
        bad_header = b"not-json{"
        data = struct.pack("<I", len(bad_header)) + bad_header
        with pytest.raises(ValueError, match="malformed"):
            Envelope.deserialize(data)

    def test_invalid_protocol(self):
        import json
        header = json.dumps({"p": "unknown_proto", "ct": "text/plain"}).encode()
        data = struct.pack("<I", len(header)) + header + b"payload"
        with pytest.raises(ValueError, match="invalid protocol"):
            Envelope.deserialize(data)

    def test_missing_protocol(self):
        import json
        header = json.dumps({"ct": "text/plain"}).encode()
        data = struct.pack("<I", len(header)) + header + b"payload"
        with pytest.raises(ValueError, match="invalid protocol"):
            Envelope.deserialize(data)


class TestEquality:
    def test_equal_envelopes(self):
        a = Envelope(Protocol.A2A, "application/json", b"test")
        b = Envelope(Protocol.A2A, "application/json", b"test")
        assert a == b

    def test_different_protocol(self):
        a = Envelope(Protocol.A2A, "application/json", b"test")
        b = Envelope(Protocol.MCP, "application/json", b"test")
        assert a != b

    def test_different_payload(self):
        a = Envelope(Protocol.A2A, "application/json", b"test1")
        b = Envelope(Protocol.A2A, "application/json", b"test2")
        assert a != b

    def test_not_equal_to_non_envelope(self):
        a = Envelope(Protocol.RAW, "text/plain", b"test")
        assert a != "not an envelope"
