import struct
import pytest

from enum import Enum

from pybusmonitor1553.lib1553.fields import Field, BitField, EnumField, ScaledField, ASCIIField
from pybusmonitor1553.lib1553.message_base import MessageBase
from pybusmonitor1553.lib1553.messages.msg_a3 import MsgA3
from pybusmonitor1553.lib1553.messages.msg_a5 import MsgA5


def test_descriptor_returns_self_on_class_access():
    class M(MessageBase):
        bf = BitField(word_index=0, start_bit=0, width=1)

    # Accessing via class should return the descriptor itself
    assert isinstance(M.bf, BitField)


def test_enumfield_auto_width_and_fallback():
    class E(Enum):
        A = 0
        B = 1
        C = 3

    class M(MessageBase):
        ef = EnumField(word_index=0, start_bit=0, enum_cls=E)  # width auto

    # Width auto-calculated to fit max value (3 -> 2 bits)
    assert M.ef.width == 2

    m = M()
    # Set raw value directly to an undefined value (e.g., 2)
    m.data[0] = 2 << (16 - (0 + M.ef.width))
    # Should return raw int because 2 not in Enum
    assert m.ef == 2


def test_asciifield_nonprintable_and_validation():
    class M(MessageBase):
        a = ASCIIField(word_index=0)

    m = M()
    # write a raw word with non-printable bytes 0x00 0x01
    m._data[0] = (0 << 8) | 1
    assert m.a == '??'

    with pytest.raises(ValueError):
        m.a = 'X'  # too short


def test_message_base_pack_unpack_and_clamp():
    class M(MessageBase):
        pass

    m = M()
    # set some sample words
    for i in range(32):
        m._data[i] = i

    packed = m.pack()
    assert len(packed) == 64

    # create raw with more than 32 words (e.g., 40 words)
    extra = struct.pack('>40H', *list(range(40)))
    m2 = M()
    m2.unpack(extra)
    # only first 32 should be loaded
    assert m2._data[31] == 31


def test_msg_a3_25bit_signed_and_ascii():
    m = MsgA3()

    # positive raw
    raw_25 = 0x00ABCDE
    msw = (raw_25 >> 9) & 0xFFFF
    lsw_fragment = raw_25 & 0x1FF
    m._data[2] = msw
    m._data[3] = (lsw_fragment << 7)
    assert isinstance(m.wp1_latitude, float)

    # negative value: set sign bit of 25-bit value
    neg_raw = (1 << 24) | 12345
    msw = (neg_raw >> 9) & 0xFFFF
    lsw_fragment = neg_raw & 0x1FF
    m._data[4] = msw
    m._data[5] = (lsw_fragment << 7)
    val = m.wp1_longitude
    assert isinstance(val, float)

    # ASCII fields
    m.hpt_callsign_ab = 'AA'
    assert m.hpt_callsign_ab == 'AA'


def test_msg_a5_32bit_signed_scaled_velocity():
    m = MsgA5()
    # build a negative 32-bit raw value
    raw32 = (1 << 31) | 12345678  # sign bit set
    msw = (raw32 >> 16) & 0xFFFF
    lsw = raw32 & 0xFFFF
    m._data[2] = msw
    m._data[3] = lsw

    expected = float((raw32 - (1 << 32)) * 3.81470e-6)
    assert pytest.approx(m.velocity_x, rel=1e-9) == expected
