#!/usr/bin/python3
"""Tests for slpkg.choose_dependencies.ChooseDependencies."""

from unittest.mock import MagicMock, patch

from slpkg.choose_dependencies import ChooseDependencies


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

DATA = {
    "ffmpeg": {"version": "6.1", "description": "A complete audio/video solution"},
    "libass": {"version": "0.17", "description": "Subtitle renderer"},
    "x264":   {"version": "164",  "description": "H.264 encoder"},
}


def make_choose_deps(
    *,
    dialog: bool = False,
    mode: str = "install",
    reinstall: bool = False,
) -> ChooseDependencies:
    """Construct ChooseDependencies bypassing __init__ (avoids config_load)."""
    obj: ChooseDependencies = object.__new__(ChooseDependencies)
    obj.repository = "sbo"
    obj.data = DATA
    obj.mode = mode
    obj.dialog = dialog
    obj.option_for_reinstall = reinstall
    obj.clear_screen = ""

    obj.upgrade = MagicMock()
    obj.upgrade.is_package_upgradeable.return_value = False

    obj.utils = MagicMock()
    obj.utils.is_package_installed.return_value = ""  # not installed by default

    obj.dialogbox = MagicMock()
    return obj


# ---------------------------------------------------------------------------
# choose — empty input
# ---------------------------------------------------------------------------


def test_choose_empty_dependencies_returns_empty() -> None:
    obj = make_choose_deps()
    view_process = MagicMock()
    result = obj.choose([], view_process)
    assert result == []
    view_process.done.assert_not_called()


# ---------------------------------------------------------------------------
# choose — package not in data is skipped
# ---------------------------------------------------------------------------


def test_choose_skips_unknown_package() -> None:
    obj = make_choose_deps(dialog=True)
    obj.dialogbox.checklist.return_value = ("ok", [])
    view_process = MagicMock()
    with patch("os.system"):
        result = obj.choose(["unknown_pkg"], view_process)
    obj.dialogbox.checklist.assert_called_once()
    # unknown_pkg was not added to choices
    choices_arg = obj.dialogbox.checklist.call_args[0][5]
    assert not any(c[0] == "unknown_pkg" for c in choices_arg)


# ---------------------------------------------------------------------------
# choose — status logic
# ---------------------------------------------------------------------------


def test_choose_installed_package_status_false() -> None:
    obj = make_choose_deps(dialog=True)
    obj.utils.is_package_installed.return_value = "ffmpeg-6.0-x86_64-1_SBo"
    obj.dialogbox.checklist.return_value = ("ok", [])
    view_process = MagicMock()
    with patch("os.system"):
        obj.choose(["ffmpeg"], view_process)
    choices = obj.dialogbox.checklist.call_args[0][5]
    ffmpeg_choice = next(c for c in choices if c[0] == "ffmpeg")
    assert ffmpeg_choice[2] is False


def test_choose_upgrade_mode_upgradeable_status_true() -> None:
    obj = make_choose_deps(dialog=True, mode="upgrade")
    obj.utils.is_package_installed.return_value = "ffmpeg-6.0-x86_64-1_SBo"
    obj.upgrade.is_package_upgradeable.return_value = True
    obj.dialogbox.checklist.return_value = ("ok", [])
    view_process = MagicMock()
    with patch("os.system"):
        obj.choose(["ffmpeg"], view_process)
    choices = obj.dialogbox.checklist.call_args[0][5]
    ffmpeg_choice = next(c for c in choices if c[0] == "ffmpeg")
    assert ffmpeg_choice[2] is True


def test_choose_reinstall_option_forces_status_true() -> None:
    obj = make_choose_deps(dialog=True, reinstall=True)
    obj.utils.is_package_installed.return_value = "ffmpeg-6.0-x86_64-1_SBo"
    obj.dialogbox.checklist.return_value = ("ok", [])
    view_process = MagicMock()
    with patch("os.system"):
        obj.choose(["ffmpeg"], view_process)
    choices = obj.dialogbox.checklist.call_args[0][5]
    ffmpeg_choice = next(c for c in choices if c[0] == "ffmpeg")
    assert ffmpeg_choice[2] is True


# ---------------------------------------------------------------------------
# choose — dialog path
# ---------------------------------------------------------------------------


def test_choose_dialog_ok_returns_selected(capsys) -> None:
    obj = make_choose_deps(dialog=True)
    obj.dialogbox.checklist.return_value = ("ok", ["ffmpeg", "libass"])
    view_process = MagicMock()
    with patch("os.system"):
        result = obj.choose(["ffmpeg", "libass"], view_process)
    assert "ffmpeg" in result
    assert "libass" in result


def test_choose_dialog_cancel_returns_empty() -> None:
    obj = make_choose_deps(dialog=True)
    obj.dialogbox.checklist.return_value = ("cancel", [])
    view_process = MagicMock()
    with patch("os.system"):
        result = obj.choose(["ffmpeg"], view_process)
    assert result == []


def test_choose_dialog_calls_view_process_done() -> None:
    obj = make_choose_deps(dialog=True)
    obj.dialogbox.checklist.return_value = ("ok", [])
    view_process = MagicMock()
    with patch("os.system"):
        obj.choose(["ffmpeg"], view_process)
    view_process.done.assert_called_once()


# ---------------------------------------------------------------------------
# choose — terminal selector path
# ---------------------------------------------------------------------------


def test_choose_terminal_selector_returns_selection() -> None:
    obj = make_choose_deps(dialog=False)
    view_process = MagicMock()
    mock_selector = MagicMock()
    mock_selector.select.return_value = ["ffmpeg"]
    with patch("slpkg.choose_dependencies.TerminalSelector", return_value=mock_selector):
        result = obj.choose(["ffmpeg"], view_process)
    assert result == ["ffmpeg"]


def test_choose_terminal_selector_called_with_correct_args() -> None:
    obj = make_choose_deps(dialog=False, mode="upgrade")
    view_process = MagicMock()
    mock_selector = MagicMock()
    mock_selector.select.return_value = []
    with patch("slpkg.choose_dependencies.TerminalSelector", return_value=mock_selector) as mock_cls:
        obj.choose(["ffmpeg"], view_process)
    # is_upgrade=True when mode=="upgrade"
    call_kwargs = mock_cls.call_args
    assert call_kwargs is not None


# ---------------------------------------------------------------------------
# _build_choices
# ---------------------------------------------------------------------------


def test_build_choices_not_installed_status_true() -> None:
    obj = make_choose_deps()
    obj.utils.is_package_installed.return_value = ""
    choices, initial = obj._build_choices(["ffmpeg"])
    assert choices[0][2] is True
    assert initial == [1]


def test_build_choices_installed_status_false() -> None:
    obj = make_choose_deps()
    obj.utils.is_package_installed.return_value = "ffmpeg-6.0-x86_64-1_SBo"
    choices, initial = obj._build_choices(["ffmpeg"])
    assert choices[0][2] is False
    assert initial == [0]


def test_build_choices_upgrade_mode_upgradeable_status_true() -> None:
    obj = make_choose_deps(mode="upgrade")
    obj.utils.is_package_installed.return_value = "ffmpeg-6.0-x86_64-1_SBo"
    obj.upgrade.is_package_upgradeable.return_value = True
    choices, initial = obj._build_choices(["ffmpeg"])
    assert choices[0][2] is True
    assert initial == [1]


def test_build_choices_reinstall_forces_status_true() -> None:
    obj = make_choose_deps(reinstall=True)
    obj.utils.is_package_installed.return_value = "ffmpeg-6.0-x86_64-1_SBo"
    choices, initial = obj._build_choices(["ffmpeg"])
    assert choices[0][2] is True
    assert initial == [1]


def test_build_choices_skips_unknown_package() -> None:
    obj = make_choose_deps()
    choices, initial = obj._build_choices(["unknown_pkg"])
    assert choices == []
    assert initial == []


def test_build_choices_multiple_packages() -> None:
    obj = make_choose_deps()
    obj.utils.is_package_installed.return_value = ""
    choices, initial = obj._build_choices(["ffmpeg", "libass"])
    assert len(choices) == 2
    assert [c[0] for c in choices] == ["ffmpeg", "libass"]


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


def test_select_via_dialog_ok_returns_selected() -> None:
    obj = make_choose_deps(dialog=True)
    obj.dialogbox.checklist.return_value = ("ok", ["ffmpeg"])
    choices = [("ffmpeg", "6.1", True, "Description: ...")]
    result = obj._select_via_dialog(choices, " Title ")
    assert result == ["ffmpeg"]


def test_select_via_dialog_cancel_returns_empty() -> None:
    obj = make_choose_deps(dialog=True)
    obj.dialogbox.checklist.return_value = ("cancel", [])
    choices = [("ffmpeg", "6.1", True, "Description: ...")]
    result = obj._select_via_dialog(choices, " Title ")
    assert result == []


def test_select_via_dialog_passes_correct_text() -> None:
    obj = make_choose_deps(dialog=True)
    obj.dialogbox.checklist.return_value = ("ok", [])
    choices = [("ffmpeg", "6.1", True, ""), ("libass", "0.17", True, "")]
    obj._select_via_dialog(choices, " Title ")
    text_arg = obj.dialogbox.checklist.call_args[0][0]
    assert "2" in text_arg


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


def test_select_via_terminal_returns_selection() -> None:
    obj = make_choose_deps()
    mock_selector = MagicMock()
    mock_selector.select.return_value = ["ffmpeg"]
    choices = [("ffmpeg", "6.1", True, "")]
    with patch("slpkg.choose_dependencies.TerminalSelector", return_value=mock_selector):
        result = obj._select_via_terminal(choices, " Title ", [1])
    assert result == ["ffmpeg"]


def test_select_via_terminal_passes_is_upgrade_true() -> None:
    obj = make_choose_deps(mode="upgrade")
    mock_selector = MagicMock()
    mock_selector.select.return_value = []
    choices = [("ffmpeg", "6.1", True, "")]
    with patch("slpkg.choose_dependencies.TerminalSelector", return_value=mock_selector) as mock_cls:
        obj._select_via_terminal(choices, " Title ", [1])
    assert mock_cls.call_args[0][3] is True


def test_select_via_terminal_passes_is_upgrade_false() -> None:
    obj = make_choose_deps(mode="install")
    mock_selector = MagicMock()
    mock_selector.select.return_value = []
    choices = [("ffmpeg", "6.1", True, "")]
    with patch("slpkg.choose_dependencies.TerminalSelector", return_value=mock_selector) as mock_cls:
        obj._select_via_terminal(choices, " Title ", [1])
    assert mock_cls.call_args[0][3] is False


def test_select_via_terminal_extracts_names_from_choices() -> None:
    obj = make_choose_deps()
    mock_selector = MagicMock()
    mock_selector.select.return_value = []
    choices = [("ffmpeg", "6.1", True, ""), ("libass", "0.17", True, "")]
    with patch("slpkg.choose_dependencies.TerminalSelector", return_value=mock_selector) as mock_cls:
        obj._select_via_terminal(choices, " Title ", [1, 1])
    assert mock_cls.call_args[0][0] == ["ffmpeg", "libass"]


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


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


def test_run_selector_dispatches_to_terminal() -> None:
    obj = make_choose_deps(dialog=False)
    mock_selector = MagicMock()
    mock_selector.select.return_value = ["libass"]
    choices = [("libass", "0.17", True, "")]
    with patch("slpkg.choose_dependencies.TerminalSelector", return_value=mock_selector):
        result = obj._run_selector(choices, " Title ", [1])
    assert result == ["libass"]
