#!/usr/bin/python3
"""Tests for slpkg.list_installed.ListInstalled."""

import json
from pathlib import Path
from unittest.mock import MagicMock

from slpkg.list_installed import ListInstalled


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

INSTALLED = {
    "ffmpeg": "ffmpeg-6.1-x86_64-1_SBo",
    "vlc": "vlc-3.0.20-x86_64-1_SBo",
    "python3": "python3-3.11.4-x86_64-1",
    "FFmpeg-extra": "FFmpeg-extra-6.1-x86_64-1_SBo",
}


def make_list_installed(
    packages: list[str],
    installed: dict[str, str],
    log_packages: Path | None = None,
    *,
    no_case: bool = False,
    description: bool = False,
    option_json: bool = False,
) -> ListInstalled:
    """Construct ListInstalled bypassing __init__ (avoids config_load)."""
    obj: ListInstalled = object.__new__(ListInstalled)
    obj.packages = packages
    obj.grey = ""
    obj.green = ""
    obj.yellow = ""
    obj.red = ""
    obj.endc = ""
    obj.log_packages = log_packages or Path("/nonexistent")
    obj.matching = []
    obj.total_size = 0
    obj.option_for_no_case = no_case
    obj.option_for_pkg_description = description
    obj.option_json = option_json

    obj.utils = MagicMock()
    obj.utils.all_installed.return_value = installed
    obj.utils.split_package.side_effect = lambda name: {"name": name.rsplit("-", 3)[0]}
    obj.utils.count_file_size.return_value = 1024
    obj.utils.convert_file_sizes.return_value = "1.0 K"
    return obj


# ---------------------------------------------------------------------------
# is_not_case_sensitive
# ---------------------------------------------------------------------------


def test_is_not_case_sensitive_disabled_returns_false() -> None:
    obj = make_list_installed([], {}, no_case=False)
    assert obj.is_not_case_sensitive("FFMPEG", "ffmpeg-6.1-x86_64-1_SBo") is False


def test_is_not_case_sensitive_enabled_match() -> None:
    obj = make_list_installed([], {}, no_case=True)
    assert obj.is_not_case_sensitive("FFMPEG", "ffmpeg-6.1-x86_64-1_SBo") is True


def test_is_not_case_sensitive_enabled_no_match() -> None:
    obj = make_list_installed([], {}, no_case=True)
    assert obj.is_not_case_sensitive("vlc", "ffmpeg-6.1-x86_64-1_SBo") is False


# ---------------------------------------------------------------------------
# installed() — matching logic
# ---------------------------------------------------------------------------


def test_installed_exact_substring_match(capsys) -> None:
    obj = make_list_installed(["ffmpeg"], INSTALLED)
    obj.installed()
    assert "ffmpeg-6.1-x86_64-1_SBo" in obj.matching


def test_installed_wildcard_matches_all(capsys) -> None:
    obj = make_list_installed(["*"], INSTALLED)
    obj.installed()
    assert len(obj.matching) == len(INSTALLED)


def test_installed_no_match_empty_matching(capsys) -> None:
    obj = make_list_installed(["nonexistent"], INSTALLED)
    obj.installed()
    assert obj.matching == []


def test_installed_case_insensitive_match(capsys) -> None:
    obj = make_list_installed(["FFMPEG"], INSTALLED, no_case=True)
    obj.installed()
    assert any("ffmpeg" in m.lower() for m in obj.matching)


def test_installed_deduplicates_multiple_queries(capsys) -> None:
    obj = make_list_installed(["ffmpeg", "ffmpeg"], INSTALLED)
    obj.installed()
    assert obj.matching.count("ffmpeg-6.1-x86_64-1_SBo") == 1


def test_installed_multiple_queries_match_distinct_packages(capsys) -> None:
    obj = make_list_installed(["ffmpeg", "vlc"], INSTALLED)
    obj.installed()
    names = obj.matching
    assert any("ffmpeg" in n for n in names)
    assert any("vlc" in n for n in names)


# ---------------------------------------------------------------------------
# view_matched_packages() — output
# ---------------------------------------------------------------------------


def test_view_matched_packages_no_match_prints_message(capsys) -> None:
    obj = make_list_installed([], {})
    obj.view_matched_packages()
    out = capsys.readouterr().out
    assert "Does not match any package" in out


def test_view_matched_packages_prints_full_name(capsys) -> None:
    obj = make_list_installed([], {})
    obj.matching = ["ffmpeg-6.1-x86_64-1_SBo"]
    obj.view_matched_packages()
    out = capsys.readouterr().out
    assert "ffmpeg-6.1-x86_64-1_SBo" in out


def test_view_matched_packages_prints_size(capsys) -> None:
    obj = make_list_installed([], {})
    obj.matching = ["ffmpeg-6.1-x86_64-1_SBo"]
    obj.view_matched_packages()
    out = capsys.readouterr().out
    assert "1.0 K" in out


# ---------------------------------------------------------------------------
# view_matched_packages() — description option
# ---------------------------------------------------------------------------


def test_view_matched_packages_description_shown(tmp_path: Path, capsys) -> None:
    pkg_log = tmp_path / "ffmpeg-6.1-x86_64-1_SBo"
    pkg_log.write_text(
        "ffmpeg: ffmpeg (A complete solution to record/convert/stream audio)\n",
        encoding="utf-8",
    )
    obj = make_list_installed([], {}, log_packages=tmp_path, description=True)
    obj.utils.split_package.return_value = {"name": "ffmpeg"}
    obj.utils.read_text_file.return_value = [
        "ffmpeg: ffmpeg (A complete solution to record/convert/stream audio)\n"
    ]
    obj.matching = ["ffmpeg-6.1-x86_64-1_SBo"]
    obj.view_matched_packages()
    out = capsys.readouterr().out
    assert "A complete solution" in out


# ---------------------------------------------------------------------------
# view_summary
# ---------------------------------------------------------------------------


def test_view_summary_shows_count_and_size(capsys) -> None:
    obj = make_list_installed([], {})
    obj.matching = ["pkg-a", "pkg-b"]
    obj.total_size = 2048
    obj.utils.convert_file_sizes.return_value = "2.0 K"
    obj.view_summary()
    out = capsys.readouterr().out
    assert "2" in out
    assert "2.0 K" in out


# ---------------------------------------------------------------------------
# view_title
# ---------------------------------------------------------------------------


def test_view_title_prints_header(capsys) -> None:
    ListInstalled.view_title()
    out = capsys.readouterr().out
    assert "Installed packages" in out


# ---------------------------------------------------------------------------
# view_matched_packages() — --json output
# ---------------------------------------------------------------------------


def _make_json_list_installed(packages: list[str]) -> ListInstalled:
    """ListInstalled instance with option_json=True and pre-populated matching."""
    obj = make_list_installed([], {}, option_json=True)
    obj.matching = packages
    obj.utils.split_package.side_effect = lambda name: {
        "name": name.rsplit("-", 3)[0],
        "version": "6.1",
        "arch": "x86_64",
        "build": "1",
        "tag": "_SBo",
    }
    obj.utils.count_file_size.return_value = 1024
    return obj


def test_json_output_is_valid_json(capsys) -> None:
    obj = _make_json_list_installed(["ffmpeg-6.1-x86_64-1_SBo"])
    obj.view_matched_packages()
    data = json.loads(capsys.readouterr().out)
    assert isinstance(data, list)


def test_json_output_contains_expected_keys(capsys) -> None:
    obj = _make_json_list_installed(["ffmpeg-6.1-x86_64-1_SBo"])
    obj.view_matched_packages()
    data = json.loads(capsys.readouterr().out)
    for item in data:
        for key in ("package", "name", "version", "arch", "build", "tag", "size_bytes"):
            assert key in item


def test_json_output_entry_count_matches(capsys) -> None:
    obj = _make_json_list_installed(["ffmpeg-6.1-x86_64-1_SBo", "vlc-3.0-x86_64-1_SBo"])
    obj.view_matched_packages()
    data = json.loads(capsys.readouterr().out)
    assert len(data) == 2


def test_json_empty_matching_outputs_empty_array(capsys) -> None:
    obj = _make_json_list_installed([])
    obj.view_matched_packages()
    data = json.loads(capsys.readouterr().out)
    assert data == []


def test_json_size_bytes_is_integer(capsys) -> None:
    obj = _make_json_list_installed(["ffmpeg-6.1-x86_64-1_SBo"])
    obj.view_matched_packages()
    data = json.loads(capsys.readouterr().out)
    assert isinstance(data[0]["size_bytes"], int)


def test_json_package_field_is_full_name(capsys) -> None:
    obj = _make_json_list_installed(["ffmpeg-6.1-x86_64-1_SBo"])
    obj.view_matched_packages()
    data = json.loads(capsys.readouterr().out)
    assert data[0]["package"] == "ffmpeg-6.1-x86_64-1_SBo"
