import os
import sys

import pytest

from src.build import BuildConfiguration, MSIXData, MSIXManager


def _in_container() -> bool:
    """Heuristic: detect common container indicators."""
    # Docker common sentinel
    if os.path.exists("/.dockerenv"):
        return True
    # cgroup heuristics (Linux only)
    try:
        with open("/proc/1/cgroup", "rt") as f:
            c = f.read()
            if any(k in c for k in ("docker", "kubepods", "containerd")):
                return True
    except Exception:
        pass
    return False


SKIP_DARWIN_OR_CONTAINER = (sys.platform == "darwin") or _in_container()


import pytest


@pytest.mark.skipif(
    SKIP_DARWIN_OR_CONTAINER,
    reason=(
        "Hardware-dependent: requires VFIO/sysfs access; skip on Darwin or in containers"
    ),
)
def test_inject_merges_table_entries_into_template_context():
    mgr = MSIXManager("0000:00:00.0")

    # Simulate msix info as would be produced by a successful preload
    fake_table_entries = [
        {"vector": 0, "data": "00112233445566778899AABBCCDDEEFF", "enabled": True}
    ]
    fake_table_init = "33221100\n77665544\nBBAA9988\nFFEEDDCC\n"

    msix_info = {
        "table_size": 1,
        "table_bir": 0,
        "table_offset": 0x80,
        "pba_bir": 0,
        "pba_offset": 0xC0,
        "enabled": True,
        "function_mask": False,
        "table_entries": fake_table_entries,
        "table_init_hex": fake_table_init,
    }

    msix_data = MSIXData(preloaded=True, msix_info=msix_info)

    result = {"template_context": {"msix_config": {}}}

    mgr.inject_data(result, msix_data)

    assert "msix_data" in result["template_context"]
    t = result["template_context"]["msix_data"]
    assert t.get("table_entries") == fake_table_entries
    assert t.get("table_init_hex") == fake_table_init


def test_preload_msix_is_enabled_by_default_in_build_config():
    cfg = BuildConfiguration("0000:00:00.0", "pcileech_35t325_x4", None)
    assert cfg.preload_msix is True


def test_msix_json_ingestion_via_env(tmp_path, monkeypatch):
    # Create a fake JSON file representing host-preloaded MSI-X
    msix_info = {
        "table_size": 8,
        "table_bir": 2,
        "table_offset": 0x180,
        "pba_bir": 2,
        "pba_offset": 0x400,
        "enabled": True,
        "function_mask": False,
    }
    payload = {
        "bdf": "0000:00:00.0",
        "msix_info": msix_info,
        "config_space_hex": "00" * 256,
    }
    json_path = tmp_path / "msix_data.json"
    json_path.write_text(__import__("json").dumps(payload))

    # Point manager to this JSON and make sysfs path look missing
    monkeypatch.setenv("MSIX_DATA_PATH", str(json_path))
    monkeypatch.setattr("os.path.exists", lambda p: p == str(json_path))

    mgr = MSIXManager("0000:00:00.0")
    data = mgr.preload_data()

    assert data.preloaded is True
    assert data.msix_info == msix_info
    assert data.config_space_hex == payload["config_space_hex"]


import os

import pytest

from src.build import MSIXManager


def make_entry(addr_low: int, addr_high: int, data: int, ctrl: int) -> bytes:
    # Little-endian packing of 4x32-bit words
    return (
        addr_low.to_bytes(4, "little")
        + addr_high.to_bytes(4, "little")
        + data.to_bytes(4, "little")
        + ctrl.to_bytes(4, "little")
    )


@pytest.mark.skipif(
    SKIP_DARWIN_OR_CONTAINER,
    reason=(
        "Hardware-dependent: requires VFIO/sysfs access; skip on Darwin or in containers"
    ),
)
def test_preload_reads_msix_table(tmp_path, monkeypatch):
    # Prepare a fake MSI-X table file with two entries at offset 128
    offset = 128
    entries = [
        make_entry(0xFEE00000, 0x0, 0x1, 0x0),
        make_entry(0xFEE00010, 0x0, 0x2, 0x0),
    ]
    table_bytes = b"".join(entries)

    table_file = tmp_path / "msix_table.bin"
    with open(table_file, "wb") as f:
        f.write(b"\x00" * offset)
        f.write(table_bytes)

    # Monkeypatch the VFIO helper used by preload_data to return FDs for our file
    def fake_get_device_fd(bdf: str):
        fd = os.open(str(table_file), os.O_RDWR)
        container_fd = os.open(str(table_file), os.O_RDWR)
        return fd, container_fd

    monkeypatch.setattr("src.cli.vfio_helpers.get_device_fd", fake_get_device_fd)
    # The code now calls ensure_device_vfio_binding before opening the device fds;
    # mock it to return a fake IOMMU group string so preload remains unit-testable.
    monkeypatch.setattr(
        "src.cli.vfio_helpers.ensure_device_vfio_binding", lambda bdf: "1"
    )

    # Monkeypatch os.path.exists to allow the config path check to pass
    monkeypatch.setattr(os.path, "exists", lambda p: True)

    # Monkeypatch parse_msix_capability to return a capability describing our table
    def fake_parse_msix_capability(hexdata: str):
        return {
            "table_size": 2,
            "table_bir": 0,
            "table_offset": offset,
            "pba_bir": 0,
            "pba_offset": offset + 2 * 16,
            "enabled": True,
            "function_mask": False,
        }

    monkeypatch.setattr("src.build.parse_msix_capability", fake_parse_msix_capability)

    mgr = MSIXManager("0000:00:00.0")
    # Avoid attempting to open the real sysfs config path; return dummy bytes
    monkeypatch.setattr(
        MSIXManager, "_read_config_space", lambda self, p: b"\x00" * 256
    )

    msix_data = mgr.preload_data()

    assert msix_data.preloaded is True
    assert msix_data.msix_info is not None
    mi = msix_data.msix_info
    assert "table_entries" in mi
    assert len(mi["table_entries"]) == 2
    # Verify that the data hex matches the entries we wrote
    assert mi["table_entries"][0]["data"] == entries[0].hex()
    assert "table_init_hex" in mi
    # table_init_hex should contain 8 words (2 entries * 4 words)
    hex_lines = [l for l in mi["table_init_hex"].splitlines() if l]
    assert len(hex_lines) == 8
