import struct

from meshy.mesh_crypto import (
    PAYLOAD_ADVERT,
    PAYLOAD_GROUP_TEXT,
    channel_hash,
    decode_log_packet,
    parse_advert_payload,
)
from meshy.models import Channel


class TestChannelHash:
    def test_deterministic(self):
        psk = "aa" * 16
        assert channel_hash(psk) == channel_hash(psk)

    def test_public_channel_psk(self):
        h = channel_hash(Channel.PUBLIC_CHANNEL_PSK)
        assert len(h) == 2
        assert all(c in "0123456789abcdef" for c in h)

    def test_different_psk_different_hash(self):
        assert channel_hash("aa" * 16) != channel_hash("bb" * 16)


class TestDecodeLogPacket:
    def test_too_short(self):
        assert decode_log_packet(b"\x00") is None

    def test_advert_packet(self):
        header = (PAYLOAD_ADVERT << 2) | 0x01  # route_type FLOOD (no transport)
        path_len_byte = 0  # direct, hash_size 1
        pub_key = bytes(32)
        timestamp = struct.pack("<I", 1700000000)
        signature = bytes(64)
        flags = 0x01  # type CHAT, no location, no name
        payload = pub_key + timestamp + signature + bytes([flags])
        raw = bytes([header, path_len_byte]) + payload
        result = decode_log_packet(raw)
        assert result is not None
        assert result["payload_type"] == PAYLOAD_ADVERT
        assert result["route_type"] == 1
        assert "advert" in result

    def test_group_text_packet(self):
        header = (PAYLOAD_GROUP_TEXT << 2) | 0x01  # FLOOD
        path_len_byte = 0
        channel_hash_byte = 0xAB
        mac = bytes(2)
        ciphertext = bytes(16)
        payload = bytes([channel_hash_byte]) + mac + ciphertext
        raw = bytes([header, path_len_byte]) + payload
        result = decode_log_packet(raw)
        assert result is not None
        assert result["payload_type"] == PAYLOAD_GROUP_TEXT
        assert result["channel_hash"] == "ab"

    def test_tc_flood_with_transport(self):
        header = (PAYLOAD_ADVERT << 2) | 0x00  # TC_FLOOD (route_type 0)
        transport = bytes(4)  # from(2) + to(2)
        path_len_byte = 0
        pub_key = bytes(32)
        timestamp = struct.pack("<I", 1700000000)
        signature = bytes(64)
        flags = 0x01
        payload = pub_key + timestamp + signature + bytes([flags])
        raw = bytes([header]) + transport + bytes([path_len_byte]) + payload
        result = decode_log_packet(raw)
        assert result is not None
        assert result["route_type"] == 0
        assert result["transport_from"] != ""

    def test_path_hash_size(self):
        header = (PAYLOAD_ADVERT << 2) | 0x01  # FLOOD
        path_len_byte = 0b01_000010  # 2 hops, hash_size 2
        path_data = bytes(4)  # 2 hops * 2 bytes
        pub_key = bytes(32)
        timestamp = struct.pack("<I", 0)
        signature = bytes(64)
        flags = 0x01
        payload = pub_key + timestamp + signature + bytes([flags])
        raw = bytes([header, path_len_byte]) + path_data + payload
        result = decode_log_packet(raw)
        assert result is not None
        assert result["path_hash_size"] == 2
        assert len(result["path"]) == 2


class TestParseAdvertPayload:
    def _build_advert(self, flags=0x01, lat=None, lon=None, name=None):
        payload = bytes(32)  # pub_key
        payload += struct.pack("<I", 1700000000)  # timestamp
        payload += bytes(64)  # signature
        payload += bytes([flags])
        if lat is not None and lon is not None:
            payload += struct.pack("<i", round(lat * 1e6))
            payload += struct.pack("<i", round(lon * 1e6))
        if name is not None:
            payload += name.encode("utf-8") + b"\x00"
        return payload

    def test_minimal(self):
        result = parse_advert_payload(self._build_advert(flags=0x01))
        assert result is not None
        assert result["type"] == 1
        assert result["adv_lat"] is None
        assert result["adv_name"] == ""

    def test_with_location(self):
        result = parse_advert_payload(self._build_advert(flags=0x11, lat=50.0, lon=14.0))
        assert abs(result["adv_lat"] - 50.0) < 0.001
        assert abs(result["adv_lon"] - 14.0) < 0.001

    def test_with_name(self):
        result = parse_advert_payload(self._build_advert(flags=0x81, name="MyNode"))
        assert result["adv_name"] == "MyNode"

    def test_with_location_and_name(self):
        result = parse_advert_payload(
            self._build_advert(flags=0x91, lat=50.0, lon=14.0, name="Node")
        )
        assert result["adv_lat"] is not None
        assert result["adv_name"] == "Node"

    def test_too_short(self):
        assert parse_advert_payload(bytes(50)) is None

    def test_zero_location_filtered(self):
        result = parse_advert_payload(self._build_advert(flags=0x11, lat=0.0, lon=0.0))
        assert result["adv_lat"] is None
        assert result["adv_lon"] is None
