"""
Unit tests for imptune.services.inf_parser.

Covers DRV-02 (INF parsing, token resolution, encoding detection,
multi-model) and DRV-05 (unused file detection).
"""
from pathlib import Path

import pytest

from imptune.services.inf_parser import ParsedInf, _detect_encoding, parse_inf

FIXTURES = Path(__file__).parent / "fixtures"

# ---------------------------------------------------------------------------
# _detect_encoding tests
# ---------------------------------------------------------------------------


def test_detect_encoding_utf16le():
    """UTF-16 LE BOM (\xff\xfe) is detected as 'utf-16'."""
    raw = b"\xff\xfe" + "[Version]\r\n".encode("utf-16-le")
    assert _detect_encoding(raw) == "utf-16"


def test_detect_encoding_utf16be():
    """UTF-16 BE BOM (\xfe\xff) is also detected as 'utf-16'."""
    raw = b"\xfe\xff" + "[Version]\r\n".encode("utf-16-be")
    assert _detect_encoding(raw) == "utf-16"


def test_detect_encoding_utf8bom():
    """UTF-8 BOM (\xef\xbb\xbf) is detected as 'utf-8-sig'."""
    raw = b"\xef\xbb\xbf" + b"[Version]\r\n"
    assert _detect_encoding(raw) == "utf-8-sig"


def test_detect_encoding_ansi():
    """Byte sequence with no BOM falls back to cp1252 (ANSI)."""
    raw = b"[Version]\r\nSignature=\"$Windows NT$\"\r\n"
    assert _detect_encoding(raw) == "cp1252"


# ---------------------------------------------------------------------------
# parse_inf – simple INF with literal driver description
# ---------------------------------------------------------------------------

SIMPLE_INF = """
[Version]
Signature="$Windows NT$"
Class=Printer

[Manufacturer]
Acme=AcmeModels,NTamd64

[AcmeModels.NTamd64]
Acme SuperPrint 9000=Install,{AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA}

[Strings]
"""


def test_simple_driver_desc():
    """parse_inf extracts literal DriverDesc from a Models section."""
    result = parse_inf(SIMPLE_INF, "acme.inf", ["acme.inf", "acme.dll"])
    assert "Acme SuperPrint 9000" in result.driver_names


# ---------------------------------------------------------------------------
# parse_inf – %TOKEN% resolution from [Strings]
# ---------------------------------------------------------------------------

TOKEN_INF = """
[Version]
Signature="$Windows NT$"

[Manufacturer]
%MFG%=Models,NTamd64

[Models.NTamd64]
%HP_DRIVER%=Install,{BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB}

[Strings]
MFG="HP"
HP_DRIVER="HP LaserJet"
"""


def test_token_resolution():
    """parse_inf resolves %TOKEN% references via the [Strings] section."""
    result = parse_inf(TOKEN_INF, "hp.inf", ["hp.inf"])
    assert "HP LaserJet" in result.driver_names
    # Raw token should NOT appear in the output
    assert "%HP_DRIVER%" not in result.driver_names


# ---------------------------------------------------------------------------
# parse_inf – UTF-16 LE fixture
# ---------------------------------------------------------------------------


def test_utf16_encoding():
    """Reading a UTF-16 LE BOM fixture and passing decoded text to parse_inf produces correct driver_names."""
    raw = (FIXTURES / "sample_utf16.inf").read_bytes()
    encoding = _detect_encoding(raw)
    assert encoding == "utf-16"
    text = raw.decode(encoding, errors="replace")
    result = parse_inf(text, "sample_utf16.inf", ["sample_utf16.inf"])
    assert "Test LaserJet Pro" in result.driver_names


# ---------------------------------------------------------------------------
# parse_inf – multi-model INF (NTamd64 + undecorated)
# ---------------------------------------------------------------------------


def test_multi_model_inf():
    """INF with both NTamd64 and undecorated sections returns deduplicated driver_names."""
    text = (FIXTURES / "sample_multi_model.inf").read_text(encoding="cp1252")
    result = parse_inf(text, "multi.inf", ["multi.inf"])
    # Both driver names must appear exactly once
    assert "Multi Printer 1000" in result.driver_names
    assert "Multi Printer 2000" in result.driver_names
    assert result.driver_names.count("Multi Printer 1000") == 1
    assert result.driver_names.count("Multi Printer 2000") == 1


# ---------------------------------------------------------------------------
# parse_inf – architecture detection
# ---------------------------------------------------------------------------

AMD64_INF = """
[Version]
Signature="$Windows NT$"

[Manufacturer]
Mfg=Mfg.Models,NTamd64

[Mfg.Models.NTamd64]
Some Driver=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}

[Strings]
"""

ARM64_INF = """
[Version]
Signature="$Windows NT$"

[Manufacturer]
Mfg=Mfg.Models,NTarm64

[Mfg.Models.NTarm64]
Some Driver=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}

[Strings]
"""

UNDECORATED_INF = """
[Version]
Signature="$Windows NT$"

[Manufacturer]
Mfg=Mfg.Models

[Mfg.Models]
Some Driver=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}

[Strings]
"""

MIXED_INF = """
[Version]
Signature="$Windows NT$"

[Manufacturer]
Mfg=Mfg.Models,Mfg.Models.NTamd64

[Mfg.Models]
Some Driver=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}

[Mfg.Models.NTamd64]
Some Driver=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}

[Strings]
"""


def test_architecture_detection_amd64():
    """NTamd64 decoration -> architecture='x64'."""
    result = parse_inf(AMD64_INF, "amd64.inf", [])
    assert result.architecture == "x64"


def test_architecture_detection_arm64():
    """NTarm64 decoration -> architecture='arm64'."""
    result = parse_inf(ARM64_INF, "arm64.inf", [])
    assert result.architecture == "arm64"


def test_architecture_detection_undecorated():
    """Undecorated section only -> architecture='x86'."""
    result = parse_inf(UNDECORATED_INF, "x86.inf", [])
    assert result.architecture == "x86"


def test_architecture_detection_mixed():
    """Mixed architectures -> architecture=None (ambiguous)."""
    result = parse_inf(MIXED_INF, "mixed.inf", [])
    assert result.architecture is None


# ---------------------------------------------------------------------------
# parse_inf – .cat file detection
# ---------------------------------------------------------------------------


def test_cat_file_detection_present():
    """zip_names containing a .cat file -> has_cat_file=True."""
    result = parse_inf(SIMPLE_INF, "acme.inf", ["acme.inf", "driver.cat", "acme.dll"])
    assert result.has_cat_file is True


def test_cat_file_detection_absent():
    """zip_names without a .cat file -> has_cat_file=False."""
    result = parse_inf(SIMPLE_INF, "acme.inf", ["acme.inf", "acme.dll"])
    assert result.has_cat_file is False


# ---------------------------------------------------------------------------
# parse_inf – unused files detection
# ---------------------------------------------------------------------------


def test_unused_files():
    """ZIP members not referenced in INF text appear in unused_files."""
    inf_text = """
[Version]
Signature="$Windows NT$"

[Manufacturer]
Mfg=Mfg.Models

[Mfg.Models]
Some Driver=Install,{DDDDDDDD-DDDD-DDDD-DDDD-DDDDDDDDDDDD}

[SourceDisksFiles]
driver.inf=1
driver.dll=1

[Strings]
"""
    zip_names = ["driver.inf", "driver.dll", "readme.txt"]
    result = parse_inf(inf_text, "driver.inf", zip_names)
    # readme.txt is not mentioned anywhere in inf_text
    assert "readme.txt" in result.unused_files
    # driver.inf and driver.dll ARE referenced in inf_text
    assert "driver.inf" not in result.unused_files
    assert "driver.dll" not in result.unused_files


# ---------------------------------------------------------------------------
# parse_inf – empty models section
# ---------------------------------------------------------------------------

EMPTY_MODELS_INF = """
[Version]
Signature="$Windows NT$"

[Manufacturer]
Mfg=MfgModels

[MfgModels]

[Strings]
"""


def test_empty_models_section():
    """INF with empty Models section returns empty driver_names list."""
    result = parse_inf(EMPTY_MODELS_INF, "empty.inf", [])
    assert result.driver_names == []


# ---------------------------------------------------------------------------
# parse_inf – tolerates bare-line sections (real-world Ricoh oemsetup.inf)
# ---------------------------------------------------------------------------

BARE_LINE_INF = """\
[Version]
Signature="$Windows NT$"

[Manufacturer]
%MFG%=Models,NTamd64

[Models.NTamd64]
%RICOH_DRIVER%=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}

[SourceDisksFiles]
ricu18ui.dll,ricu18ui.dl_
ricu18ui.irj
ricu18ui.rdj
ricu18gl.dll,ricu18gl.dl_
RD01Kd64.dll,RD01Kd64.dl_,,0x00000020

[Strings]
MFG="Ricoh"
RICOH_DRIVER="Ricoh PCL6 Universal"
"""


def test_bare_line_sections_do_not_raise():
    """Real INFs (e.g. Ricoh oemsetup.inf) include [SourceDisksFiles] entries
    with bare filename lines and no '=' — parse_inf must not raise and must
    still extract DriverDesc from the Models section."""
    result = parse_inf(BARE_LINE_INF, "oemsetup.inf", ["oemsetup.inf"])
    assert "Ricoh PCL6 Universal" in result.driver_names
    # Synthetic __bare_N keys must not leak into driver_names
    assert not any(n.startswith("__bare_") for n in result.driver_names)
