#!/usr/bin/python3
"""Tests for pure static methods in slpkg.utilities.Utilities."""

import pytest

from slpkg.utilities import Utilities

# ---------------------------------------------------------------------------
# split_package
# ---------------------------------------------------------------------------

SPLIT_CASES = [
    # (package_string, expected_dict)
    (
        "ffmpeg-7.1-x86_64-1alien",
        {"name": "ffmpeg", "version": "7.1", "arch": "x86_64", "build": "1", "tag": "alien"},
    ),
    (
        "bash-5.2.026-x86_64-1",
        {"name": "bash", "version": "5.2.026", "arch": "x86_64", "build": "1", "tag": ""},
    ),
    (
        "a-1-x86_64-1",
        {"name": "a", "version": "1", "arch": "x86_64", "build": "1", "tag": ""},
    ),
    (
        "perl-5.38-noarch-1",
        {"name": "perl", "version": "5.38", "arch": "noarch", "build": "1", "tag": ""},
    ),
    (
        "python3-3.11.5-x86_64-2SBo",
        {"name": "python3", "version": "3.11.5", "arch": "x86_64", "build": "2", "tag": "SBo"},
    ),
    (
        "libpng-1.6.43-x86_64-3",
        {"name": "libpng", "version": "1.6.43", "arch": "x86_64", "build": "3", "tag": ""},
    ),
    (
        "lib-foo-bar-1.0-x86_64-1alien",
        {"name": "lib-foo-bar", "version": "1.0", "arch": "x86_64", "build": "1", "tag": "alien"},
    ),
    (
        "kernel-5.15.0_smp-x86_64-2",
        {"name": "kernel", "version": "5.15.0_smp", "arch": "x86_64", "build": "2", "tag": ""},
    ),
    (
        "foo-1.0-x86_64-10alien",
        {"name": "foo", "version": "1.0", "arch": "x86_64", "build": "10", "tag": "alien"},
    ),
    (
        "x264-20240224-x86_64-1alien",
        {"name": "x264", "version": "20240224", "arch": "x86_64", "build": "1", "tag": "alien"},
    ),
    (
        "yasm-1.3.0-x86_64-3alien",
        {"name": "yasm", "version": "1.3.0", "arch": "x86_64", "build": "3", "tag": "alien"},
    ),
    # Empty string → all-empty defaults
    (
        "",
        {"name": "", "version": "", "arch": "", "build": "", "tag": ""},
    ),
    # Too few segments → all-empty defaults
    (
        "abc-1-2",
        {"name": "", "version": "", "arch": "", "build": "", "tag": ""},
    ),
]


@pytest.mark.parametrize("package,expected", SPLIT_CASES)
def test_split_package(package: str, expected: dict) -> None:
    assert Utilities.split_package(package) == expected


# ---------------------------------------------------------------------------
# convert_file_sizes
# ---------------------------------------------------------------------------

CONVERT_CASES = [
    # (byte_size, expected_string)
    (0, "0 B"),
    (512, "512 B"),
    (1023, "1023 B"),
    (1024, "1.0 KB"),
    (1536, "1.5 KB"),
    (2048, "2.0 KB"),
    (1024 * 1024, "1.0 MB"),
    (int(6.7 * 1024 * 1024), "6.7 MB"),
    (2 * 1024 * 1024, "2.0 MB"),
    (1024**3, "1.0 GB"),
    (2 * 1024**3, "2.0 GB"),
]


@pytest.mark.parametrize("byte_size,expected", CONVERT_CASES)
def test_convert_file_sizes(byte_size: int, expected: str) -> None:
    assert Utilities.convert_file_sizes(byte_size) == expected


# ---------------------------------------------------------------------------
# apply_package_pattern
# ---------------------------------------------------------------------------


def test_apply_package_pattern_wildcard_returns_all_keys(sample_data: dict) -> None:
    result = Utilities.apply_package_pattern(sample_data, ["*"])
    assert sorted(result) == sorted(sample_data.keys())


def test_apply_package_pattern_wildcard_with_extra_package(sample_data: dict) -> None:
    result = Utilities.apply_package_pattern(sample_data, ["ffmpeg", "*"])
    assert "ffmpeg" in result
    for key in sample_data:
        assert key in result


def test_apply_package_pattern_no_wildcard_unchanged(sample_data: dict) -> None:
    packages = ["ffmpeg", "bash"]
    result = Utilities.apply_package_pattern(sample_data, packages)
    assert result == ["ffmpeg", "bash"]


def test_apply_package_pattern_nonexistent_unchanged(sample_data: dict) -> None:
    packages = ["nonexistent"]
    result = Utilities.apply_package_pattern(sample_data, packages)
    assert result == ["nonexistent"]


def test_apply_package_pattern_empty_packages(sample_data: dict) -> None:
    result = Utilities.apply_package_pattern(sample_data, [])
    assert result == []


def test_apply_package_pattern_empty_data() -> None:
    result = Utilities.apply_package_pattern({}, ["*"])
    assert result == []


# ---------------------------------------------------------------------------
# case_insensitive_pattern_matching
# ---------------------------------------------------------------------------


def test_case_insensitive_uppercase_matches(sample_data: dict) -> None:
    packages = ["FFMPEG"]
    result = Utilities.case_insensitive_pattern_matching(packages, sample_data, {"option_no_case": True})
    assert result == ["ffmpeg"]


def test_case_insensitive_mixed_case_matches(sample_data: dict) -> None:
    packages = ["FfMpEg"]
    result = Utilities.case_insensitive_pattern_matching(packages, sample_data, {"option_no_case": True})
    assert result == ["ffmpeg"]


def test_case_insensitive_exact_match_unchanged(sample_data: dict) -> None:
    packages = ["ffmpeg"]
    result = Utilities.case_insensitive_pattern_matching(packages, sample_data, {"option_no_case": True})
    assert result == ["ffmpeg"]


def test_case_insensitive_no_match_stays_unchanged(sample_data: dict) -> None:
    packages = ["nonexistent"]
    result = Utilities.case_insensitive_pattern_matching(packages, sample_data, {"option_no_case": True})
    assert result == ["nonexistent"]


def test_case_insensitive_option_off_no_change(sample_data: dict) -> None:
    packages = ["FFMPEG"]
    result = Utilities.case_insensitive_pattern_matching(packages, sample_data, {"option_no_case": False})
    assert result == ["FFMPEG"]


def test_case_insensitive_option_missing_no_change(sample_data: dict) -> None:
    packages = ["FFMPEG"]
    result = Utilities.case_insensitive_pattern_matching(packages, sample_data, {})
    assert result == ["FFMPEG"]


def test_case_insensitive_multiple_packages(sample_data: dict) -> None:
    packages = ["FFMPEG", "BASH", "YASM"]
    result = Utilities.case_insensitive_pattern_matching(packages, sample_data, {"option_no_case": True})
    assert result == ["ffmpeg", "bash", "yasm"]


def test_case_insensitive_empty_packages(sample_data: dict) -> None:
    result = Utilities.case_insensitive_pattern_matching([], sample_data, {"option_no_case": True})
    assert result == []


# ---------------------------------------------------------------------------
# package_log_size
# ---------------------------------------------------------------------------


def _make_utils(log_packages_path: object) -> Utilities:
    """Build a Utilities instance with a custom log_packages path."""
    utils: Utilities = object.__new__(Utilities)
    utils.log_packages = log_packages_path
    return utils


def _write_log(tmp_path: object, filename: str, size_line: str, extra: str = "") -> None:
    """Write a minimal /var/log/packages-style log file."""
    import pathlib
    content = (
        f"PACKAGE NAME:     {filename}\n"
        f"COMPRESSED PACKAGE SIZE:     100K\n"
        f"{size_line}\n"
        f"PACKAGE LOCATION: /tmp/{filename}.txz\n"
        f"{extra}"
    )
    pathlib.Path(tmp_path, filename).write_text(content, encoding="utf-8")


def test_package_log_size_kilobytes(tmp_path) -> None:
    _write_log(tmp_path, "pkg-1.0-x86_64-1", "UNCOMPRESSED PACKAGE SIZE:     640K")
    utils = _make_utils(tmp_path)
    assert utils.package_log_size("pkg-1.0-x86_64-1") == int(640 * 1024)


def test_package_log_size_megabytes(tmp_path) -> None:
    _write_log(tmp_path, "pkg-1.0-x86_64-1", "UNCOMPRESSED PACKAGE SIZE:     1.3M")
    utils = _make_utils(tmp_path)
    assert utils.package_log_size("pkg-1.0-x86_64-1") == int(1.3 * 1024 ** 2)


def test_package_log_size_gigabytes(tmp_path) -> None:
    _write_log(tmp_path, "pkg-1.0-x86_64-1", "UNCOMPRESSED PACKAGE SIZE:     2G")
    utils = _make_utils(tmp_path)
    assert utils.package_log_size("pkg-1.0-x86_64-1") == 2 * 1024 ** 3


def test_package_log_size_missing_file(tmp_path) -> None:
    utils = _make_utils(tmp_path)
    assert utils.package_log_size("nonexistent-1.0-x86_64-1") == 0


def test_package_log_size_no_size_line(tmp_path) -> None:
    import pathlib
    content = "PACKAGE NAME:     pkg-1.0-x86_64-1\nPACKAGE LOCATION: /tmp/pkg.txz\n"
    pathlib.Path(tmp_path, "pkg-1.0-x86_64-1").write_text(content, encoding="utf-8")
    utils = _make_utils(tmp_path)
    assert utils.package_log_size("pkg-1.0-x86_64-1") == 0


def test_package_log_size_malformed_value(tmp_path) -> None:
    _write_log(tmp_path, "pkg-1.0-x86_64-1", "UNCOMPRESSED PACKAGE SIZE:     ???K")
    utils = _make_utils(tmp_path)
    assert utils.package_log_size("pkg-1.0-x86_64-1") == 0


def test_package_log_size_lowercase_suffix(tmp_path) -> None:
    # suffix should be case-insensitive
    _write_log(tmp_path, "pkg-1.0-x86_64-1", "UNCOMPRESSED PACKAGE SIZE:     512k")
    utils = _make_utils(tmp_path)
    assert utils.package_log_size("pkg-1.0-x86_64-1") == int(512 * 1024)


# ---------------------------------------------------------------------------
# _blacklist_pattern
# ---------------------------------------------------------------------------

@pytest.mark.parametrize("entry,expected", [
    # Plain names: special regex chars that appear in Slackware package names
    # must be escaped so they are matched literally.
    ("akonadi",        r"akonadi"),
    ("gtk+2",          r"gtk\+2"),
    ("libsigc++",      r"libsigc\+\+"),
    ("perl-5.38",      r"perl\-5\.38"),
    # Regex patterns: entries with unambiguous regex metacharacters are used as-is.
    ("^akonadi$",      r"^akonadi$"),
    ("^akonadi-.*$",   r"^akonadi-.*$"),
    ("kde.*",          r"kde.*"),
    ("^(kde|plasma)",  r"^(kde|plasma)"),
    ("[Kk]de",         r"[Kk]de"),
])
def test_blacklist_pattern(entry: str, expected: str) -> None:
    assert Utilities._blacklist_pattern(entry) == expected
