#!/usr/bin/python3
"""Tests for slpkg.binaries.install.Packages pure list-manipulation methods."""

from unittest.mock import MagicMock

from slpkg.binaries.install import Packages


def make_packages(
    data: dict,
    packages: list,
    dependencies: list | None = None,
    install_order: list | None = None,
    mode: str = "install",
    option_for_reinstall: bool = False,
    option_for_skip_installed: bool = False,
    options: dict | None = None,
) -> Packages:
    """Construct Packages bypassing __init__ (avoids config_load and all heavy dependencies)."""
    pkg: Packages = object.__new__(Packages)
    pkg.data = data
    pkg.packages = list(packages)
    pkg.dependencies = list(dependencies or [])
    pkg.install_order = list(install_order or [])
    pkg.mode = mode
    pkg.options = dict(options or {})
    pkg.option_for_reinstall = option_for_reinstall
    pkg.option_for_skip_installed = option_for_skip_installed
    pkg.progress_message = "Installing"
    pkg.skipped_packages = []
    pkg.utils = MagicMock()
    return pkg


# ---------------------------------------------------------------------------
# clean_the_main_slackbuilds()
# Removes from self.packages any package that is already in self.dependencies.
# ---------------------------------------------------------------------------


def test_clean_removes_packages_in_deps(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=["ffmpeg", "yasm"], dependencies=["yasm"])
    pkg.clean_the_main_slackbuilds()
    assert "yasm" not in pkg.packages
    assert "ffmpeg" in pkg.packages


def test_clean_no_overlap_unchanged(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=["ffmpeg", "bash"], dependencies=["yasm"])
    pkg.clean_the_main_slackbuilds()
    assert pkg.packages == ["ffmpeg", "bash"]


def test_clean_all_packages_are_deps(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=["yasm", "bash"], dependencies=["yasm", "bash", "libvpx"])
    pkg.clean_the_main_slackbuilds()
    assert pkg.packages == []


def test_clean_empty_dependencies_unchanged(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=["ffmpeg", "bash"], dependencies=[])
    pkg.clean_the_main_slackbuilds()
    assert pkg.packages == ["ffmpeg", "bash"]


def test_clean_empty_packages_stays_empty(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=[], dependencies=["yasm"])
    pkg.clean_the_main_slackbuilds()
    assert pkg.packages == []


# ---------------------------------------------------------------------------
# add_dependencies_to_install_order()
# Extends self.install_order with self.dependencies.
# ---------------------------------------------------------------------------


def test_add_deps_to_install_order(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=[], dependencies=["yasm", "libvpx"])
    pkg.add_dependencies_to_install_order()
    assert pkg.install_order == ["yasm", "libvpx"]


def test_add_deps_appends_to_existing_order(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=[], dependencies=["yasm"], install_order=["bash"])
    pkg.add_dependencies_to_install_order()
    assert pkg.install_order == ["bash", "yasm"]


def test_add_empty_deps_no_change(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=[], dependencies=[], install_order=["bash"])
    pkg.add_dependencies_to_install_order()
    assert pkg.install_order == ["bash"]


# ---------------------------------------------------------------------------
# add_main_packages_to_install_order()
# Extends self.install_order with self.packages.
# ---------------------------------------------------------------------------


def test_add_main_packages_to_install_order(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=["ffmpeg"], install_order=["yasm"])
    pkg.add_main_packages_to_install_order()
    assert pkg.install_order == ["yasm", "ffmpeg"]


def test_add_main_packages_empty_packages_no_change(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=[], install_order=["yasm"])
    pkg.add_main_packages_to_install_order()
    assert pkg.install_order == ["yasm"]


def test_add_main_packages_to_empty_order(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=["ffmpeg", "bash"])
    pkg.add_main_packages_to_install_order()
    assert pkg.install_order == ["ffmpeg", "bash"]


# ---------------------------------------------------------------------------
# set_progress_message()
# mode='upgrade' OR option_for_reinstall → 'Upgrading'
# otherwise → 'Installing' (default)
# ---------------------------------------------------------------------------


def test_set_progress_message_install_mode(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=[], mode="install")
    pkg.set_progress_message()
    assert pkg.progress_message == "Installing"


def test_set_progress_message_upgrade_mode(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=[], mode="upgrade")
    pkg.set_progress_message()
    assert pkg.progress_message == "Upgrading"


def test_set_progress_message_reinstall_option(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=[], mode="install", option_for_reinstall=True)
    pkg.set_progress_message()
    assert pkg.progress_message == "Upgrading"


def test_set_progress_message_both_upgrade_and_reinstall(sample_data: dict) -> None:
    pkg = make_packages(sample_data, packages=[], mode="upgrade", option_for_reinstall=True)
    pkg.set_progress_message()
    assert pkg.progress_message == "Upgrading"


# ---------------------------------------------------------------------------
# execute() dry-run (option_check / --dry-run)
# With option_check=True, execute() shows the view table but does NOT call
# view.question() or install_packages().
# ---------------------------------------------------------------------------


def test_execute_dry_run_skips_install_packages(sample_data: dict) -> None:
    """--dry-run: view table is displayed but install_packages() is never called."""
    pkg = make_packages(sample_data, packages=["ffmpeg"], options={"option_check": True})
    pkg.preselected_deps = []
    pkg.view = MagicMock()
    install_mock = MagicMock()
    pkg.install_packages = install_mock

    pkg.execute()

    pkg.view.install_upgrade_packages.assert_called_once()
    pkg.view.missing_dependencies.assert_called_once()
    pkg.view.question.assert_not_called()
    install_mock.assert_not_called()


def test_execute_dry_run_with_dependencies(sample_data: dict) -> None:
    """--dry-run passes dependencies correctly to install_upgrade_packages()."""
    pkg = make_packages(sample_data, packages=["ffmpeg"], options={"option_check": True})
    pkg.preselected_deps = ["yasm", "libvpx"]
    pkg.view = MagicMock()
    pkg.install_packages = MagicMock()

    pkg.execute()

    call_args = pkg.view.install_upgrade_packages.call_args
    _, deps, _ = call_args[0]
    assert "yasm" in deps
    assert "libvpx" in deps


def test_execute_normal_calls_question_and_install(sample_data: dict) -> None:
    """Without --dry-run, execute() calls view.question() and install_packages()."""
    pkg = make_packages(sample_data, packages=["ffmpeg"], options={})
    pkg.preselected_deps = []
    pkg.view = MagicMock()
    install_mock = MagicMock()
    pkg.install_packages = install_mock
    pkg.creating_the_package_urls_list = MagicMock()
    pkg.checksum_binary_packages = MagicMock()
    pkg.set_progress_message = MagicMock()
    pkg.utils.finished_time = MagicMock()

    pkg.execute()

    pkg.view.question.assert_called_once()
    install_mock.assert_called_once()


# ---------------------------------------------------------------------------
# install_packages() — command tool, history_action, package_to_repo
# ---------------------------------------------------------------------------


def _make_for_install(
    mode: str = "install",
    option_for_reinstall: bool = False,
    package_to_repo: dict | None = None,
) -> Packages:
    """Set up a Packages instance ready for install_packages() calls."""
    from pathlib import Path
    from unittest.mock import patch

    data = {"mypkg": {"requires": ""}}
    pkg = make_packages(data, packages=[], mode=mode, option_for_reinstall=option_for_reinstall)
    pkg.repository = "sbo"
    pkg.installpkg = "installpkg"
    pkg.reinstall = "upgradepkg --reinstall"
    pkg.tmp_slpkg = Path("/tmp/slpkg")
    pkg.delete_sources = False
    pkg.binary_packages = ["mypkg-1.0-x86_64-1_SBo.txz"]
    pkg.package_to_repo = package_to_repo
    pkg.multi_proc = MagicMock()
    pkg.multi_proc.process_and_log.return_value = True
    pkg.history_logger = MagicMock()
    pkg.dependency_logger = MagicMock()
    pkg.process_log_file = MagicMock()
    pkg.utils.split_package.return_value = {"name": "mypkg"}
    with patch("slpkg.binaries.install.Required") as mock_req, \
         patch("slpkg.binaries.install.config_load") as mock_cfg, \
         patch("slpkg.binaries.install.shutil"):
        mock_req.return_value.resolve.return_value = ()
        mock_cfg.sep_line = "-"
        pkg._mock_req = mock_req
        pkg._mock_cfg = mock_cfg
    return pkg


def test_install_packages_uses_installpkg_by_default(sample_data):
    from pathlib import Path
    from unittest.mock import patch

    pkg = make_packages(sample_data, packages=[], mode="install")
    pkg.repository = "sbo"
    pkg.installpkg = "installpkg"
    pkg.reinstall = "upgradepkg --reinstall"
    pkg.tmp_slpkg = Path("/tmp/slpkg")
    pkg.delete_sources = False
    pkg.binary_packages = ["mypkg-1.0-x86_64-1_SBo.txz"]
    pkg.package_to_repo = None
    pkg.multi_proc = MagicMock()
    pkg.multi_proc.process_and_log.return_value = False  # skip history
    pkg.history_logger = MagicMock()
    pkg.dependency_logger = MagicMock()
    pkg.process_log_file = MagicMock()
    pkg.utils.split_package.return_value = {"name": "mypkg"}

    with patch("slpkg.binaries.install.config_load") as mock_cfg, \
         patch("slpkg.binaries.install.shutil"):
        mock_cfg.sep_line = "-"
        pkg.install_packages()

    cmd = pkg.multi_proc.process_and_log.call_args[0][0]
    assert cmd.startswith("installpkg ")


def test_install_packages_uses_reinstall_tool(sample_data):
    from pathlib import Path
    from unittest.mock import patch

    pkg = make_packages(sample_data, packages=[], mode="install", option_for_reinstall=True)
    pkg.repository = "sbo"
    pkg.installpkg = "installpkg"
    pkg.reinstall = "upgradepkg --reinstall"
    pkg.tmp_slpkg = Path("/tmp/slpkg")
    pkg.delete_sources = False
    pkg.binary_packages = ["mypkg-1.0-x86_64-1_SBo.txz"]
    pkg.package_to_repo = None
    pkg.multi_proc = MagicMock()
    pkg.multi_proc.process_and_log.return_value = False
    pkg.history_logger = MagicMock()
    pkg.dependency_logger = MagicMock()
    pkg.process_log_file = MagicMock()
    pkg.utils.split_package.return_value = {"name": "mypkg"}

    with patch("slpkg.binaries.install.config_load") as mock_cfg, \
         patch("slpkg.binaries.install.shutil"):
        mock_cfg.sep_line = "-"
        pkg.install_packages()

    cmd = pkg.multi_proc.process_and_log.call_args[0][0]
    assert cmd.startswith("upgradepkg --reinstall ")


def _run_install_packages(mode: str, option_for_reinstall: bool, package_to_repo=None):
    """Helper: run install_packages() and return the history_logger.write call args."""
    from pathlib import Path
    from unittest.mock import patch

    data = {"mypkg": {"requires": ""}}
    pkg = make_packages(data, packages=[], mode=mode, option_for_reinstall=option_for_reinstall)
    pkg.repository = "sbo"
    pkg.installpkg = "installpkg"
    pkg.reinstall = "upgradepkg --reinstall"
    pkg.tmp_slpkg = Path("/tmp/slpkg")
    pkg.delete_sources = False
    pkg.binary_packages = ["mypkg-1.0-x86_64-1_SBo.txz"]
    pkg.package_to_repo = package_to_repo
    pkg.multi_proc = MagicMock()
    pkg.multi_proc.process_and_log.return_value = True
    pkg.history_logger = MagicMock()
    pkg.dependency_logger = MagicMock()
    pkg.process_log_file = MagicMock()
    pkg.utils.split_package.return_value = {"name": "mypkg"}

    with patch("slpkg.binaries.install.Required") as mock_req, \
         patch("slpkg.binaries.install.config_load") as mock_cfg, \
         patch("slpkg.binaries.install.shutil"):
        mock_req.return_value.resolve.return_value = ()
        mock_cfg.sep_line = "-"
        pkg.install_packages()

    return pkg.history_logger.write.call_args[0]


def test_history_action_install():
    action, *_ = _run_install_packages("install", option_for_reinstall=False)
    assert action == "install"


def test_history_action_upgrade():
    action, *_ = _run_install_packages("upgrade", option_for_reinstall=False)
    assert action == "upgrade"


def test_history_action_reinstall():
    action, *_ = _run_install_packages("install", option_for_reinstall=True)
    assert action == "reinstall"


def test_package_to_repo_uses_mapped_repo():
    _, name, _, repo = _run_install_packages("install", False, package_to_repo={"mypkg": "alien"})
    assert repo == "alien"


def test_package_to_repo_falls_back_to_default_repo():
    _, name, _, repo = _run_install_packages("install", False, package_to_repo={"other": "alien"})
    assert repo == "sbo"


def test_package_to_repo_none_uses_default_repo():
    _, name, _, repo = _run_install_packages("install", False, package_to_repo=None)
    assert repo == "sbo"
