"""Offline round-trip tests for the LAN7800 image model. No hardware.

Run: python3 -m pytest cli/tests   (or)   python3 cli/tests/test_eeprom.py
"""
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from lan7800.eeprom import Eeprom, EepromError, LAN7800_VID, LAN7800_PID  # noqa
from tests.make_fixture import build  # noqa


def _fx():
    return Eeprom.from_bytes(build())


def test_signature_and_size():
    e = _fx()
    assert e.size == 512
    assert e.has_signature and e.signature == 0xA5


def test_mac_exact_bytes_natural_order():
    # RESOLVED assumption: EEPROM offset 0x01 = addr[0] (natural order).
    # Entering 00:80:0F:78:00:00 must store bytes 00 80 0F 78 00 00 at 0x01,
    # so the lan78xx driver reads the intended MAC back (not the byte-reverse).
    from lan7800.eeprom import MAC_OFFSET
    e = Eeprom.blank(512)
    e.set_mac("00:80:0F:78:00:00")
    assert bytes(e.data[MAC_OFFSET:MAC_OFFSET + 6]) == \
        bytes([0x00, 0x80, 0x0F, 0x78, 0x00, 0x00])
    assert e.mac == "00:80:0F:78:00:00"
    # and the reverse would have been the bug we fixed
    assert e.mac != "00:00:78:0F:80:00"


def test_mac_roundtrip():
    e = _fx()
    assert e.mac == "00:80:0F:11:22:33"
    e.set_mac("AA:BB:CC:DD:EE:FF")
    assert e.mac == "AA:BB:CC:DD:EE:FF"
    # re-parse persisted bytes to prove octet order survives a save/load
    e2 = Eeprom.from_bytes(e.to_bytes())
    assert e2.mac == "AA:BB:CC:DD:EE:FF"


def test_vid_pid_across_all_blocks():
    e = _fx()
    for d in e.descriptors():
        assert d.present and d.looks_valid
        assert d.vid == LAN7800_VID and d.pid == LAN7800_PID
    touched = e.set_vid(0x1234)
    assert set(touched) == {"ss", "hs", "fs"}
    e.set_pid(0xABCD)
    e2 = Eeprom.from_bytes(e.to_bytes())
    for d in e2.descriptors():
        assert d.vid == 0x1234 and d.pid == 0xABCD


def test_bcd_device():
    e = _fx()
    e.set_bcd_device(0x0410)
    e2 = Eeprom.from_bytes(e.to_bytes())
    assert all(d.bcd_device == 0x0410 for d in e2.descriptors() if d.present)


def test_strings_read():
    e = _fx()
    s = {x.name: x.value for x in e.strings() if x.present}
    assert s["manufacturer"] == "Microchip"
    assert s["product"] == "LAN7800 USB 3.1 GbE"
    assert s["serial"] == "SN0000000001"


def test_serial_inplace_shorter_ok():
    e = _fx()
    e.set_string_inplace("serial", "SN42")  # shorter -> fits
    e2 = Eeprom.from_bytes(e.to_bytes())
    assert e2.string("serial").value == "SN42"


def test_serial_inplace_too_long_refused():
    e = _fx()
    try:
        e.set_string_inplace("serial", "X" * 40)  # far larger than slot
    except EepromError:
        return
    raise AssertionError("expected EepromError for oversized in-place string")


def test_set_string_absent_refused():
    e = _fx()
    try:
        e.set_string_inplace("interface", "eth")  # absent in fixture
    except EepromError:
        return
    raise AssertionError("expected EepromError setting an absent string")


def test_signature_setter():
    e = Eeprom.blank(512)
    assert not e.has_signature
    e.set_signature()
    assert e.has_signature


def test_bad_mac_rejected():
    e = _fx()
    for bad in ("00:11:22:33:44", "zz:11:22:33:44:55", "1:2:3:4:5:6:7"):
        try:
            e.set_mac(bad)
        except EepromError:
            continue
        raise AssertionError(f"expected rejection of {bad!r}")


def test_led_roundtrip():
    from lan7800 import regs
    e = _fx()
    regs.set_led_mode(e, 0, 10); regs.set_led_enable(e, 0, True)
    regs.set_led_mode(e, 3, 15); regs.set_led_blink(e, 3)
    e2 = Eeprom.from_bytes(e.to_bytes())
    assert regs.get_led(e2, 0)["mode"] == 10 and regs.get_led(e2, 0)["enabled"]
    assert regs.get_led(e2, 3)["mode_name"] == "Force On"
    assert regs.get_led_blink(e2)["blink_rate"] == "20Hz"


def test_gpio_roundtrip():
    from lan7800 import regs
    e = _fx()
    regs.set_gpio(e, 5, direction="out", drive="open-drain", output_value=1,
                  wake_enabled=True, wake_active_high=False)
    e2 = Eeprom.from_bytes(e.to_bytes())
    g = regs.get_gpio(e2, 5)
    assert g["direction"] == "out" and g["drive"] == "open-drain"
    assert g["output_value"] == 1 and g["wake_enabled"] and not g["wake_active_high"]


def test_regs_bounds():
    from lan7800 import regs
    e = _fx()
    for bad in (-1, 4, 99):
        try:
            regs.set_led_mode(e, bad, 0)
        except EepromError:
            continue
        raise AssertionError("expected LED index rejection")


def test_default_template():
    e = Eeprom.default_template()
    assert e.has_signature
    assert e.mac == "00:80:0F:00:00:00"
    ds = [d for d in e.descriptors() if d.present]
    assert len(ds) == 3
    assert all(d.vid == LAN7800_VID and d.pid == LAN7800_PID for d in ds)
    from lan7800 import regs
    assert regs.region_overlaps(e) == []   # descriptors clear of LED/GPIO offsets


def test_overlap_guard():
    from lan7800 import regs
    from lan7800.eeprom import DESC_PTRS
    e = _fx()
    # Point the SS descriptor at 0x48 (word 0x24) so its 18-byte block covers
    # the fixed GPIO config region, and make it look like a real descriptor.
    e.data[DESC_PTRS["ss"]] = 0x24
    e.data[DESC_PTRS["ss"] + 1] = 0x00
    e.data[0x48] = 0x12
    e.data[0x49] = 0x01
    overlaps = regs.region_overlaps(e)
    assert overlaps, "region_overlaps should detect the collision"
    try:
        regs.set_gpio(e, 5, output_value=1)
    except EepromError:
        return
    raise AssertionError("guard should block a GPIO write into a descriptor block")


def _run():
    fns = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
    passed = 0
    for fn in fns:
        fn()
        passed += 1
        print(f"  ok  {fn.__name__}")
    print(f"\n{passed}/{len(fns)} passed")


if __name__ == "__main__":
    _run()
