import struct
from pm3py.transport import encode_ng_frame, decode_response_frame, encode_mix_frame, _crc_to_wire
from pm3py.protocol import Cmd, CMD_PREAMBLE_MAGIC, CMD_POSTAMBLE_NOCRC, RESP_PREAMBLE_MAGIC, RESP_POSTAMBLE_NOCRC, crc16_a

def test_encode_ng_frame_ping_no_payload():
    frame = encode_ng_frame(Cmd.PING, b"")
    # 8 byte preamble + 0 payload + 2 postamble = 10 bytes
    assert len(frame) == 10
    magic, length_ng, cmd = struct.unpack_from("<IHH", frame, 0)
    assert magic == CMD_PREAMBLE_MAGIC
    assert length_ng & 0x8000  # ng bit set
    assert (length_ng & 0x7FFF) == 0
    assert cmd == Cmd.PING
    # Default: no CRC (USB mode)
    crc_actual = struct.unpack_from("<H", frame, 8)[0]
    assert crc_actual == CMD_POSTAMBLE_NOCRC

def test_encode_ng_frame_with_payload_no_crc():
    """Default USB mode: no CRC, uses magic postamble."""
    payload = bytes(range(32))
    frame = encode_ng_frame(Cmd.PING, payload)
    assert len(frame) == 10 + 32
    _, length_ng, _ = struct.unpack_from("<IHH", frame, 0)
    assert (length_ng & 0x7FFF) == 32
    assert frame[8:40] == payload
    crc_actual = struct.unpack_from("<H", frame, 40)[0]
    assert crc_actual == CMD_POSTAMBLE_NOCRC

def test_encode_ng_frame_with_crc():
    """FPC UART mode: real CRC, byte-swapped."""
    payload = bytes(range(32))
    frame = encode_ng_frame(Cmd.PING, payload, use_crc=True)
    crc_expected = _crc_to_wire(crc16_a(frame[:40]))
    crc_actual = struct.unpack_from("<H", frame, 40)[0]
    assert crc_actual == crc_expected

def test_encode_mix_frame():
    frame = encode_mix_frame(Cmd.HF_ISO14443A_READER, 0x107, 0, 0, b"")
    _, length_ng, _ = struct.unpack_from("<IHH", frame, 0)
    assert not (length_ng & 0x8000)  # ng bit NOT set for MIX
    assert (length_ng & 0x7FFF) == 24  # 3x uint64 = 24 bytes

def test_decode_response_frame_no_crc():
    """USB mode: response uses no-CRC magic."""
    payload = b"\x01\x02\x03\x04"
    length_ng = len(payload) | 0x8000
    preamble = struct.pack("<IHbbH", RESP_PREAMBLE_MAGIC, length_ng, 0, 0, Cmd.PING)
    body = preamble + payload
    postamble = struct.pack("<H", RESP_POSTAMBLE_NOCRC)
    frame = body + postamble

    resp = decode_response_frame(frame)
    assert resp["cmd"] == Cmd.PING
    assert resp["status"] == 0
    assert resp["data"] == payload
    assert resp["ng"] is True

def test_decode_response_frame_with_crc():
    """FPC UART mode: response has real CRC, byte-swapped."""
    payload = b"\x01\x02\x03\x04"
    length_ng = len(payload) | 0x8000
    preamble = struct.pack("<IHbbH", RESP_PREAMBLE_MAGIC, length_ng, 0, 0, Cmd.PING)
    body = preamble + payload
    crc = _crc_to_wire(crc16_a(body))
    postamble = struct.pack("<H", crc)
    frame = body + postamble

    resp = decode_response_frame(frame)
    assert resp["cmd"] == Cmd.PING
    assert resp["status"] == 0
    assert resp["data"] == payload

def test_decode_response_frame_error_status():
    length_ng = 0 | 0x8000
    preamble = struct.pack("<IHbbH", RESP_PREAMBLE_MAGIC, length_ng, -4, -1, Cmd.STATUS)
    postamble = struct.pack("<H", RESP_POSTAMBLE_NOCRC)
    frame = preamble + postamble

    resp = decode_response_frame(frame)
    assert resp["status"] == -4  # ETIMEOUT
    assert resp["data"] == b""
