from __future__ import annotations

import errno
import mmap
import os
from pathlib import Path
import tempfile

from asm3042_flasher.device import AsmediaXhciController, DeviceInfo, ensure_protocol_supported


def test_allowlist_accepts_asm3142() -> None:
    info = DeviceInfo(
        bdf="0000:01:00.0",
        vendor_id=0x1B21,
        device_id=0x3142,
        class_code=0x0C0330,
        driver="xhci_hcd",
        sysfs_path=Path("/tmp/fake"),
    )

    ensure_protocol_supported(info, force=False)


def test_open_falls_back_to_devmem_when_resource0_mmap_fails(monkeypatch) -> None:
    with tempfile.TemporaryDirectory() as tmp:
        sysfs_path = Path(tmp)
        config_path = sysfs_path / "config"
        resource0_path = sysfs_path / "resource0"
        resource_path = sysfs_path / "resource"
        enable_path = sysfs_path / "enable"
        devmem_path = sysfs_path / "devmem.bin"

        config_path.write_bytes(b"\x00" * 4096)
        resource0_path.write_bytes(b"\x00" * 0x8000)
        resource_path.write_text("0x2000 0x9fff 0x0\n")
        enable_path.write_text("0\n")
        devmem_path.write_bytes(b"\x00" * 0x20000)

        info = DeviceInfo(
            bdf="0000:01:00.0",
            vendor_id=0x1B21,
            device_id=0x3142,
            class_code=0x0C0330,
            driver="xhci_hcd",
            sysfs_path=sysfs_path,
        )
        controller = AsmediaXhciController(info)

        original_open = os.open
        original_mmap = mmap.mmap
        calls = {"count": 0}

        def fake_open(path, flags, mode=0o777):
            if os.fspath(path) == "/dev/mem":
                return original_open(devmem_path, flags, mode)
            return original_open(path, flags, mode)

        def fake_mmap(fd, length, flags=mmap.MAP_SHARED, prot=mmap.PROT_READ | mmap.PROT_WRITE, access=None, offset=0):
            calls["count"] += 1
            if calls["count"] == 1:
                raise OSError(errno.EINVAL, "simulated resource0 mmap failure")
            if access is not None:
                return original_mmap(fd, length, access=access, offset=offset)
            return original_mmap(fd, length, flags=flags, prot=prot, offset=offset)

        monkeypatch.setattr(os, "open", fake_open)
        monkeypatch.setattr(mmap, "mmap", fake_mmap)

        with controller:
            controller.write_mmio_u32(0x10, 0x11223344)
            assert controller.read_mmio_u32(0x10) == 0x11223344


def test_open_config_only_does_not_require_resource0() -> None:
    with tempfile.TemporaryDirectory() as tmp:
        sysfs_path = Path(tmp)
        (sysfs_path / "config").write_bytes(b"\x00" * 4096)
        (sysfs_path / "enable").write_text("0\n")

        info = DeviceInfo(
            bdf="0000:01:00.0",
            vendor_id=0x1B21,
            device_id=0x3142,
            class_code=0x0C0330,
            driver="xhci_hcd",
            sysfs_path=sysfs_path,
        )

        with AsmediaXhciController(info) as controller:
            assert controller.read_config_u8(0) == 0
