#!/usr/bin/python3
"""Tests for slpkg.choose_packages.Choose."""

import pytest
from unittest.mock import MagicMock, patch

from slpkg.choose_packages import Choose


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

REPO_DATA = {
    "ffmpeg": {"version": "6.1", "build": "1", "package": "ffmpeg-6.1-x86_64-1_SBo.txz"},
    "vlc":    {"version": "3.0.20", "build": "1", "package": "vlc-3.0.20-x86_64-1_SBo.txz"},
    "x264":   {"version": "164",    "build": "1", "package": "x264-164-x86_64-1_SBo.txz"},
}

INSTALLED = {
    "ffmpeg": "ffmpeg-6.0-x86_64-1_SBo",
    "vlc":    "vlc-3.0.19-x86_64-1_SBo",
}


def make_choose(
    *,
    dialog: bool = False,
    repository: str = "sbo",
    select: bool = False,
) -> Choose:
    """Construct Choose bypassing __init__ (avoids config_load)."""
    obj: Choose = object.__new__(Choose)
    obj.repository = repository
    obj.options = {"option_select": select}
    obj.dialog = dialog
    obj.bold = ""
    obj.green = ""
    obj.red = ""
    obj.endc = ""
    obj.clear_screen = ""
    obj.choices = []
    obj.height = 10
    obj.width = 70
    obj.list_height = 0
    obj.match_packages = []
    obj.option_for_select_packages = select

    obj.utils = MagicMock()
    obj.utils.all_installed.return_value = INSTALLED
    obj.utils.is_package_installed.return_value = ""
    obj.utils.split_package.side_effect = lambda name: {
        "name": name.rsplit("-", 3)[0],
        "version": name.rsplit("-", 3)[1] if len(name.rsplit("-", 3)) > 1 else "",
        "arch":    name.rsplit("-", 3)[2] if len(name.rsplit("-", 3)) > 2 else "",
        "build":   name.rsplit("-", 3)[3] if len(name.rsplit("-", 3)) > 3 else "",
    }

    obj.dialogbox = MagicMock()
    return obj


# ---------------------------------------------------------------------------
# _iter_packages
# ---------------------------------------------------------------------------


def test_iter_packages_single_repo_yields_all() -> None:
    obj = make_choose(repository="sbo")
    result = list(obj._iter_packages(REPO_DATA))
    names = [r[0] for r in result]
    assert set(names) == {"ffmpeg", "vlc", "x264"}


def test_iter_packages_single_repo_yields_correct_repo_name() -> None:
    obj = make_choose(repository="sbo")
    result = list(obj._iter_packages(REPO_DATA))
    assert all(repo == "sbo" for _, _, repo in result)


def test_iter_packages_single_repo_yields_version() -> None:
    obj = make_choose(repository="sbo")
    result = {name: ver for name, ver, _ in obj._iter_packages(REPO_DATA)}
    assert result["ffmpeg"] == "6.1"
    assert result["vlc"] == "3.0.20"


def test_iter_packages_multi_repo_yields_from_all_repos() -> None:
    obj = make_choose(repository="*")
    multi_data = {"sbo": REPO_DATA, "alien": {"gimp": {"version": "2.10"}}}
    result = list(obj._iter_packages(multi_data))
    names = [r[0] for r in result]
    assert "ffmpeg" in names
    assert "gimp" in names


def test_iter_packages_multi_repo_yields_correct_repo_names() -> None:
    obj = make_choose(repository="*")
    multi_data = {"sbo": {"ffmpeg": {"version": "6.1"}}, "alien": {"gimp": {"version": "2.10"}}}
    result = {name: repo for name, _, repo in obj._iter_packages(multi_data)}
    assert result["ffmpeg"] == "sbo"
    assert result["gimp"] == "alien"


def test_iter_packages_empty_data_yields_nothing() -> None:
    obj = make_choose(repository="sbo")
    assert list(obj._iter_packages({})) == []


# ---------------------------------------------------------------------------
# choose_from_installed
# ---------------------------------------------------------------------------


def test_choose_from_installed_matches_substring(capsys) -> None:
    obj = make_choose()
    obj.choose_from_installed(["ffmpeg"])
    assert "ffmpeg" in obj.match_packages


def test_choose_from_installed_wildcard_matches_all(capsys) -> None:
    obj = make_choose()
    obj.choose_from_installed(["*"])
    assert len(obj.match_packages) == len(INSTALLED)


def test_choose_from_installed_no_match_empty(capsys) -> None:
    obj = make_choose()
    obj.choose_from_installed(["nonexistent"])
    assert obj.match_packages == []


def test_choose_from_installed_deduplicates(capsys) -> None:
    obj = make_choose()
    obj.choose_from_installed(["ffmpeg", "ffmpeg"])
    assert obj.match_packages.count("ffmpeg") == 1


# ---------------------------------------------------------------------------
# choose_for_upgraded
# ---------------------------------------------------------------------------


def test_choose_for_upgraded_adds_installed_package() -> None:
    obj = make_choose()
    obj.utils.is_package_installed.return_value = "ffmpeg-6.0-x86_64-1_SBo"
    obj.choose_for_upgraded(REPO_DATA, ["ffmpeg"])
    assert "ffmpeg" in obj.match_packages
    assert len(obj.choices) == 1


def test_choose_for_upgraded_new_package_labeled_add() -> None:
    obj = make_choose()
    obj.utils.is_package_installed.return_value = ""
    obj.choose_for_upgraded(REPO_DATA, ["ffmpeg"])
    label = obj.choices[0][1]
    assert "Add" in label


def test_choose_for_upgraded_skips_unknown_package() -> None:
    obj = make_choose()
    obj.choose_for_upgraded(REPO_DATA, ["nonexistent"])
    assert obj.match_packages == []


def test_choose_for_upgraded_adds_multiple_packages() -> None:
    obj = make_choose()
    obj.utils.is_package_installed.return_value = ""
    obj.choose_for_upgraded(REPO_DATA, ["ffmpeg", "vlc"])
    assert "ffmpeg" in obj.match_packages
    assert "vlc" in obj.match_packages


# ---------------------------------------------------------------------------
# choose_for_others — single repo
# ---------------------------------------------------------------------------


def test_choose_for_others_single_repo_matches_substring() -> None:
    obj = make_choose(repository="sbo")
    obj.choose_for_others(REPO_DATA, ["ffmpeg"])
    assert "ffmpeg" in obj.match_packages


def test_choose_for_others_single_repo_wildcard_matches_all() -> None:
    obj = make_choose(repository="sbo")
    obj.choose_for_others(REPO_DATA, ["*"])
    assert len(obj.match_packages) == len(REPO_DATA)


def test_choose_for_others_single_repo_deduplicates() -> None:
    obj = make_choose(repository="sbo")
    obj.choose_for_others(REPO_DATA, ["ffmpeg", "ffmpeg"])
    assert obj.match_packages.count("ffmpeg") == 1


# ---------------------------------------------------------------------------
# choose_for_others — multi-repo (*)
# ---------------------------------------------------------------------------


def test_choose_for_others_multi_repo_matches_across_repos() -> None:
    obj = make_choose(repository="*")
    multi_data = {"sbo": REPO_DATA, "alien": {"ffmpeg-alien": {"version": "6.1"}}}
    obj.choose_for_others(multi_data, ["ffmpeg"])
    assert "ffmpeg" in obj.match_packages
    assert "ffmpeg-alien" in obj.match_packages


def test_choose_for_others_multi_repo_deduplicates() -> None:
    obj = make_choose(repository="*")
    multi_data = {"sbo": REPO_DATA, "other": REPO_DATA}
    obj.choose_for_others(multi_data, ["ffmpeg"])
    assert obj.match_packages.count("ffmpeg") == 1


# ---------------------------------------------------------------------------
# _select_via_dialog
# ---------------------------------------------------------------------------


def test_select_via_dialog_returns_selected_packages() -> None:
    obj = make_choose(dialog=True)
    obj.choices = [("ffmpeg", "6.1", False, ""), ("vlc", "3.0.20", False, "")]
    obj.dialogbox.checklist.return_value = ("ok", ["ffmpeg"])
    result = obj._select_via_dialog(" Title ", ordered=False)
    assert result == ["ffmpeg"]


def test_select_via_dialog_sorts_choices_when_ordered() -> None:
    obj = make_choose(dialog=True)
    obj.choices = [("vlc", "3.0.20", False, ""), ("ffmpeg", "6.1", False, "")]
    obj.dialogbox.checklist.return_value = ("ok", ["ffmpeg"])
    obj._select_via_dialog(" Title ", ordered=True)
    assert obj.choices[0][0] == "ffmpeg"


def test_select_via_dialog_cancel_raises_systemexit() -> None:
    obj = make_choose(dialog=True)
    obj.choices = [("ffmpeg", "6.1", False, "")]
    obj.dialogbox.checklist.return_value = ("cancel", [])
    with pytest.raises(SystemExit) as exc:
        obj._select_via_dialog(" Title ", ordered=False)
    assert exc.value.code == 0


def test_select_via_dialog_empty_selection_raises_systemexit() -> None:
    obj = make_choose(dialog=True)
    obj.choices = [("ffmpeg", "6.1", False, "")]
    obj.dialogbox.checklist.return_value = ("ok", [])
    with pytest.raises(SystemExit) as exc:
        obj._select_via_dialog(" Title ", ordered=False)
    assert exc.value.code == 0


# ---------------------------------------------------------------------------
# _select_via_terminal
# ---------------------------------------------------------------------------


def test_select_via_terminal_returns_selected_packages() -> None:
    obj = make_choose(dialog=False)
    obj.match_packages = ["ffmpeg", "vlc"]
    mock_selector = MagicMock()
    mock_selector.select.return_value = ["ffmpeg"]
    with patch("slpkg.choose_packages.TerminalSelector", return_value=mock_selector):
        result = obj._select_via_terminal(" Title ", REPO_DATA, False, ordered=False)
    assert result == ["ffmpeg"]


def test_select_via_terminal_sorts_packages_when_ordered() -> None:
    obj = make_choose(dialog=False)
    obj.match_packages = ["vlc", "ffmpeg"]
    mock_selector = MagicMock()
    mock_selector.select.return_value = []
    with patch("slpkg.choose_packages.TerminalSelector", return_value=mock_selector):
        obj._select_via_terminal(" Title ", REPO_DATA, False, ordered=True)
    assert obj.match_packages == ["ffmpeg", "vlc"]


def test_select_via_terminal_initial_selection_all_by_default() -> None:
    obj = make_choose(dialog=False, select=False)
    obj.match_packages = ["ffmpeg"]
    mock_selector = MagicMock()
    mock_selector.select.return_value = ["ffmpeg"]
    with patch("slpkg.choose_packages.TerminalSelector", return_value=mock_selector) as mock_cls:
        obj._select_via_terminal(" Title ", REPO_DATA, False, ordered=False)
    assert mock_cls.call_args[1]["initial_selection"] == "all"


def test_select_via_terminal_initial_selection_none_when_select_option() -> None:
    obj = make_choose(dialog=False, select=True)
    obj.match_packages = ["ffmpeg"]
    mock_selector = MagicMock()
    mock_selector.select.return_value = ["ffmpeg"]
    with patch("slpkg.choose_packages.TerminalSelector", return_value=mock_selector) as mock_cls:
        obj._select_via_terminal(" Title ", REPO_DATA, False, ordered=False)
    assert mock_cls.call_args[1]["initial_selection"] is None


def test_select_via_terminal_passes_is_upgrade() -> None:
    obj = make_choose(dialog=False)
    obj.match_packages = ["ffmpeg"]
    mock_selector = MagicMock()
    mock_selector.select.return_value = []
    with patch("slpkg.choose_packages.TerminalSelector", return_value=mock_selector) as mock_cls:
        obj._select_via_terminal(" Title ", REPO_DATA, True, ordered=False)
    assert mock_cls.call_args[0][3] is True


# ---------------------------------------------------------------------------
# _run_selector
# ---------------------------------------------------------------------------


def test_run_selector_dispatches_to_dialog() -> None:
    obj = make_choose(dialog=True)
    obj.choices = [("ffmpeg", "6.1", False, "")]
    obj.dialogbox.checklist.return_value = ("ok", ["ffmpeg"])
    result = obj._run_selector(" Title ", REPO_DATA, False, ordered=False)
    assert result == ["ffmpeg"]


def test_run_selector_dispatches_to_terminal() -> None:
    obj = make_choose(dialog=False)
    obj.match_packages = ["ffmpeg"]
    mock_selector = MagicMock()
    mock_selector.select.return_value = ["ffmpeg"]
    with patch("slpkg.choose_packages.TerminalSelector", return_value=mock_selector):
        result = obj._run_selector(" Title ", REPO_DATA, False, ordered=False)
    assert result == ["ffmpeg"]


# ---------------------------------------------------------------------------
# packages — dispatch
# ---------------------------------------------------------------------------


def test_packages_remove_calls_choose_from_installed() -> None:
    obj = make_choose(dialog=True)
    obj.dialogbox.checklist.return_value = ("ok", ["ffmpeg"])
    result = obj.packages(REPO_DATA, ["ffmpeg"], "remove")
    assert "ffmpeg" in result


def test_packages_cancel_raises_systemexit() -> None:
    obj = make_choose(dialog=True)
    obj.dialogbox.checklist.return_value = ("cancel", [])
    obj.choices = [("ffmpeg", "6.1", False, "")]
    obj.match_packages = ["ffmpeg"]
    with pytest.raises(SystemExit) as exc:
        obj.packages(REPO_DATA, ["ffmpeg"], "install")
    assert exc.value.code == 0


def test_packages_no_choices_returns_original_list() -> None:
    obj = make_choose(dialog=True)
    obj.utils.all_installed.return_value = {}
    result = obj.packages(REPO_DATA, ["ffmpeg"], "remove")
    assert result == ["ffmpeg"]


def test_packages_terminal_selector_install() -> None:
    obj = make_choose(dialog=False)
    mock_selector = MagicMock()
    mock_selector.select.return_value = ["ffmpeg"]
    with patch("slpkg.choose_packages.TerminalSelector", return_value=mock_selector):
        result = obj.packages(REPO_DATA, ["ffmpeg"], "install")
    assert "ffmpeg" in result


def test_packages_upgrade_sets_is_upgrade_flag() -> None:
    obj = make_choose(dialog=False)
    obj.utils.is_package_installed.return_value = "ffmpeg-6.0-x86_64-1_SBo"
    mock_selector = MagicMock()
    mock_selector.select.return_value = ["ffmpeg"]
    with patch("slpkg.choose_packages.TerminalSelector", return_value=mock_selector) as mock_cls:
        obj.packages(REPO_DATA, ["ffmpeg"], "upgrade")
    # is_upgrade=True should be passed to TerminalSelector
    args = mock_cls.call_args[0]
    assert args[3] is True  # is_upgrade positional arg
