#!/usr/bin/python3
"""Tests for slpkg.views.display.View pure summary-generation methods."""

from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from slpkg.upgrade import Upgrade
from slpkg.utilities import Utilities
from slpkg.views.display import View


def make_view() -> View:
    """Construct View bypassing __init__ (avoids config_load/Repositories/Upgrade)."""
    v: View = object.__new__(View)
    v.grey = ""
    v.green = ""
    v.yellow = ""
    v.red = ""
    v.endc = ""
    v.tmp_path = Path("/tmp/slpkg")
    v.download_only = Path("/tmp/downloads")
    v.summary_message = ""
    v.view_missing_deps = True
    v.data = {}
    # Wire convert_file_sizes through the real static method.
    v.utils = MagicMock()
    v.utils.convert_file_sizes.side_effect = Utilities.convert_file_sizes
    return v


def make_view_full() -> View:
    """Extend make_view() with attributes needed by _imprint_and_tally / install_upgrade_packages_all."""
    v = make_view()
    v.sum_install = 0
    v.sum_upgrade = 0
    v.sum_size_comp = 0.0
    v.sum_size_uncomp = 0.0
    v.columns = 80

    v.imp = MagicMock()

    mock_repos = MagicMock()
    mock_repos.sbo_repo_name = "sbo"
    mock_repos.ponce_repo_name = "ponce"
    v.repos = mock_repos

    return v


# ---------------------------------------------------------------------------
# set_summary_for_build(packages)
# ---------------------------------------------------------------------------


def test_set_summary_for_build_contains_count() -> None:
    v = make_view()
    v.set_summary_for_build(["ffmpeg", "yasm", "libvpx"])
    assert "3" in v.summary_message


def test_set_summary_for_build_contains_build_keyword() -> None:
    v = make_view()
    v.set_summary_for_build(["ffmpeg"])
    assert "built" in v.summary_message


def test_set_summary_for_build_contains_tmp_path() -> None:
    v = make_view()
    v.set_summary_for_build(["ffmpeg"])
    assert str(v.tmp_path) in v.summary_message


def test_set_summary_for_build_empty_packages() -> None:
    v = make_view()
    v.set_summary_for_build([])
    assert "0" in v.summary_message


# ---------------------------------------------------------------------------
# set_summary_for_install_and_upgrade(install, upgrade, size_comp, size_uncomp)
# ---------------------------------------------------------------------------


def test_set_summary_install_upgrade_contains_install_count() -> None:
    v = make_view()
    v.set_summary_for_install_and_upgrade(3, 1, 0, 0)
    assert "3" in v.summary_message


def test_set_summary_install_upgrade_contains_upgrade_count() -> None:
    v = make_view()
    v.set_summary_for_install_and_upgrade(3, 2, 0, 0)
    assert "2" in v.summary_message


def test_set_summary_install_upgrade_contains_install_keyword() -> None:
    v = make_view()
    v.set_summary_for_install_and_upgrade(1, 0, 0, 0)
    assert "install" in v.summary_message.lower()


def test_set_summary_install_upgrade_contains_upgraded_keyword() -> None:
    v = make_view()
    v.set_summary_for_install_and_upgrade(0, 2, 0, 0)
    assert "upgraded" in v.summary_message.lower()


def test_set_summary_install_upgrade_contains_size() -> None:
    v = make_view()
    v.set_summary_for_install_and_upgrade(1, 0, 1024, 2048)
    # convert_file_sizes(1024) = '1.0 KB'
    assert "1.0 KB" in v.summary_message


def test_set_summary_install_upgrade_no_size_line_when_zero() -> None:
    # For SBo/Ponce repos sizes are always 0 — the size line must be hidden.
    v = make_view()
    v.set_summary_for_install_and_upgrade(5, 2, 0, 0)
    assert "downloaded" not in v.summary_message
    assert "0 B" not in v.summary_message


# ---------------------------------------------------------------------------
# set_summary_for_remove(remove, size_rmv)
# ---------------------------------------------------------------------------


def test_set_summary_for_remove_contains_count() -> None:
    v = make_view()
    v.set_summary_for_remove(5, 0)
    assert "5" in v.summary_message


def test_set_summary_for_remove_contains_removed_keyword() -> None:
    v = make_view()
    v.set_summary_for_remove(1, 0)
    assert "removed" in v.summary_message.lower()


def test_set_summary_for_remove_contains_freed_keyword() -> None:
    v = make_view()
    v.set_summary_for_remove(1, 0)
    assert "freed" in v.summary_message.lower()


def test_set_summary_for_remove_contains_size() -> None:
    v = make_view()
    v.set_summary_for_remove(1, 1024 * 1024)
    assert "1.0 MB" in v.summary_message


# ---------------------------------------------------------------------------
# set_summary_for_download(packages, size_comp)
# ---------------------------------------------------------------------------


def test_set_summary_for_download_contains_count() -> None:
    v = make_view()
    v.set_summary_for_download(["ffmpeg", "yasm"], 0)
    assert "2" in v.summary_message


def test_set_summary_for_download_contains_downloaded_keyword() -> None:
    v = make_view()
    v.set_summary_for_download(["ffmpeg"], 0)
    assert "downloaded" in v.summary_message.lower()


def test_set_summary_for_download_contains_directory() -> None:
    v = make_view()
    v.set_summary_for_download(["ffmpeg"], 0)
    assert str(v.download_only) in v.summary_message


def test_set_summary_for_download_contains_size() -> None:
    v = make_view()
    v.set_summary_for_download(["ffmpeg"], 2 * 1024 * 1024)
    assert "2.0 MB" in v.summary_message


# ---------------------------------------------------------------------------
# missing_dependencies(packages)
# Finds packages in install_order whose requires contain names not in self.data
# ---------------------------------------------------------------------------


def test_missing_dependencies_none_missing(capsys, sample_data: dict) -> None:
    v = make_view()
    v.data = sample_data
    # ffmpeg requires libvpx and x264, both in sample_data
    v.missing_dependencies(["ffmpeg"])
    captured = capsys.readouterr()
    assert "missing" not in captured.out.lower()


def test_missing_dependencies_ghost_dep_shown(capsys, sample_data: dict) -> None:
    v = make_view()
    data = dict(sample_data)
    data["mypkg"] = {"version": "1.0", "requires": ["ghost_lib"]}
    v.data = data
    v.missing_dependencies(["mypkg"])
    captured = capsys.readouterr()
    assert "ghost_lib" in captured.out


def test_missing_dependencies_view_disabled(capsys, sample_data: dict) -> None:
    v = make_view()
    v.view_missing_deps = False
    data = dict(sample_data)
    data["mypkg"] = {"version": "1.0", "requires": ["ghost_lib"]}
    v.data = data
    v.missing_dependencies(["mypkg"])
    captured = capsys.readouterr()
    # view_missing_deps disabled → nothing printed
    assert "ghost_lib" not in captured.out


# ---------------------------------------------------------------------------
# _imprint_and_tally(package, data, upgrade, repo_name, is_sbo)
# ---------------------------------------------------------------------------


def _make_upgrade_mock(upgradeable: bool = False) -> Upgrade:
    m = MagicMock(spec=Upgrade)
    m.is_package_upgradeable.return_value = upgradeable
    return m


def test_imprint_and_tally_not_installed_increments_sum_install() -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = ""
    upgrade = _make_upgrade_mock(upgradeable=False)
    data = {"ffmpeg": {"version": "7.1", "size_comp": "10", "size_uncomp": "20"}}

    v._imprint_and_tally("ffmpeg", data, upgrade, "alien", is_sbo=False)

    assert v.sum_install == 1
    assert v.sum_upgrade == 0


def test_imprint_and_tally_installed_upgradeable_increments_sum_upgrade() -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = "ffmpeg-7.0-x86_64-1alien.txz"
    v.utils.split_package.return_value = {"version": "7.0"}
    upgrade = _make_upgrade_mock(upgradeable=True)
    data = {"ffmpeg": {"version": "7.1", "size_comp": "10", "size_uncomp": "20"}}

    v._imprint_and_tally("ffmpeg", data, upgrade, "alien", is_sbo=False)

    assert v.sum_upgrade == 1
    assert v.sum_install == 0


def test_imprint_and_tally_installed_not_upgradeable_no_count_change() -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = "ffmpeg-7.1-x86_64-1alien.txz"
    upgrade = _make_upgrade_mock(upgradeable=False)
    data = {"ffmpeg": {"version": "7.1", "size_comp": "10", "size_uncomp": "20"}}

    v._imprint_and_tally("ffmpeg", data, upgrade, "alien", is_sbo=False)

    assert v.sum_install == 0
    assert v.sum_upgrade == 0


def test_imprint_and_tally_binary_repo_accumulates_sizes() -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = ""
    upgrade = _make_upgrade_mock()
    data = {"ffmpeg": {"version": "7.1", "size_comp": "100", "size_uncomp": "200"}}

    v._imprint_and_tally("ffmpeg", data, upgrade, "alien", is_sbo=False)

    assert v.sum_size_comp == 100 * 1024
    assert v.sum_size_uncomp == 200 * 1024


def test_imprint_and_tally_sbo_repo_no_size_accumulated() -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = ""
    upgrade = _make_upgrade_mock()
    data = {"vmpk": {"version": "0.9.0", "size_comp": "50", "size_uncomp": "100"}}

    v._imprint_and_tally("vmpk", data, upgrade, "ponce", is_sbo=True)

    assert v.sum_size_comp == 0.0
    assert v.sum_size_uncomp == 0.0


def test_imprint_and_tally_calls_package_line() -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = ""
    upgrade = _make_upgrade_mock()
    data = {"ffmpeg": {"version": "7.1", "size_comp": "10", "size_uncomp": "20"}}

    v._imprint_and_tally("ffmpeg", data, upgrade, "alien", is_sbo=False)

    v.imp.package_line.assert_called_once()


def test_imprint_and_tally_accumulates_across_multiple_calls() -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = ""
    upgrade = _make_upgrade_mock()
    data = {
        "ffmpeg": {"version": "7.1", "size_comp": "10", "size_uncomp": "20"},
        "libvpx": {"version": "1.13", "size_comp": "5", "size_uncomp": "10"},
    }

    v._imprint_and_tally("ffmpeg", data, upgrade, "alien", is_sbo=False)
    v._imprint_and_tally("libvpx", data, upgrade, "alien", is_sbo=False)

    assert v.sum_install == 2
    assert v.sum_size_comp == (10 + 5) * 1024


# ---------------------------------------------------------------------------
# install_upgrade_packages_all(repo_data)
# ---------------------------------------------------------------------------


def test_install_upgrade_packages_all_sets_mode_upgrade(capsys) -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = ""
    v.utils.split_package.return_value = {"version": "1.0"}
    data = {"ffmpeg": {"version": "7.1", "size_comp": "0", "size_uncomp": "0"}}
    repo_data = [("alien", data, ["ffmpeg"], [])]

    v.install_upgrade_packages_all(repo_data)

    assert v.mode == "upgrade"


def test_install_upgrade_packages_all_calls_package_status(capsys) -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = ""
    data = {"ffmpeg": {"version": "7.1", "size_comp": "0", "size_uncomp": "0"}}
    repo_data = [("alien", data, ["ffmpeg"], [])]

    v.install_upgrade_packages_all(repo_data)

    v.imp.package_status.assert_called_once_with("Upgrading:")


def test_install_upgrade_packages_all_calls_dependency_status_when_deps_exist(capsys) -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = ""
    data = {
        "ffmpeg": {"version": "7.1", "size_comp": "0", "size_uncomp": "0"},
        "libvpx": {"version": "1.13", "size_comp": "0", "size_uncomp": "0"},
    }
    repo_data = [("alien", data, ["ffmpeg"], ["libvpx"])]

    v.install_upgrade_packages_all(repo_data)

    v.imp.dependency_status.assert_called_once_with("Upgrading")


def test_install_upgrade_packages_all_no_dependency_status_when_no_deps(capsys) -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = ""
    data = {"ffmpeg": {"version": "7.1", "size_comp": "0", "size_uncomp": "0"}}
    repo_data = [("alien", data, ["ffmpeg"], [])]

    v.install_upgrade_packages_all(repo_data)

    v.imp.dependency_status.assert_not_called()


def test_install_upgrade_packages_all_multi_repo_counts_all_packages(capsys) -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = ""
    data_alien = {"ffmpeg": {"version": "7.1", "size_comp": "0", "size_uncomp": "0"}}
    data_slack = {"bash": {"version": "5.2", "size_comp": "0", "size_uncomp": "0"}}
    repo_data = [
        ("alien", data_alien, ["ffmpeg"], []),
        ("slack", data_slack, ["bash"], []),
    ]

    v.install_upgrade_packages_all(repo_data)

    assert v.sum_install == 2


def test_install_upgrade_packages_all_prints_summary(capsys) -> None:
    v = make_view_full()
    v.utils.is_package_installed.return_value = ""
    data = {"ffmpeg": {"version": "7.1", "size_comp": "0", "size_uncomp": "0"}}
    repo_data = [("alien", data, ["ffmpeg"], [])]

    v.install_upgrade_packages_all(repo_data)

    captured = capsys.readouterr()
    assert "summary" in captured.out.lower()


# ---------------------------------------------------------------------------
# _pinned_notice(upgrades)
# ---------------------------------------------------------------------------


def _make_view_for_pinned() -> View:
    v = make_view_full()
    v.utils.convert_file_sizes.side_effect = Utilities.convert_file_sizes
    return v


def _make_upgrade_for_pinned(upgradeable: bool) -> Upgrade:
    m = MagicMock(spec=Upgrade)
    m.is_package_upgradeable.return_value = upgradeable
    return m


def test_pinned_notice_no_pins_prints_nothing(capsys) -> None:
    v = _make_view_for_pinned()
    v.upgrade = _make_upgrade_for_pinned(upgradeable=False)
    with patch("slpkg.views.display.Pin") as MockPin:
        MockPin.return_value.read_pins.return_value = set()
        v._pinned_notice()
    assert capsys.readouterr().out == ""


def test_pinned_notice_pinned_not_installed_prints_nothing(capsys) -> None:
    v = _make_view_for_pinned()
    v.utils.is_package_installed.return_value = ""
    upgrade = _make_upgrade_for_pinned(upgradeable=False)
    with patch("slpkg.views.display.Pin") as MockPin:
        MockPin.return_value.read_pins.return_value = {"firefox"}
        v._pinned_notice([upgrade])
    assert capsys.readouterr().out == ""


def test_pinned_notice_pinned_installed_not_upgradeable_prints_nothing(capsys) -> None:
    v = _make_view_for_pinned()
    v.utils.is_package_installed.return_value = "firefox-130.0-x86_64-1alien.txz"
    upgrade = _make_upgrade_for_pinned(upgradeable=False)
    with patch("slpkg.views.display.Pin") as MockPin:
        MockPin.return_value.read_pins.return_value = {"firefox"}
        v._pinned_notice([upgrade])
    assert capsys.readouterr().out == ""


def test_pinned_notice_pinned_installed_upgradeable_prints_notice(capsys) -> None:
    v = _make_view_for_pinned()
    v.utils.is_package_installed.return_value = "firefox-130.0-x86_64-1alien.txz"
    upgrade = _make_upgrade_for_pinned(upgradeable=True)
    with patch("slpkg.views.display.Pin") as MockPin:
        MockPin.return_value.read_pins.return_value = {"firefox"}
        v._pinned_notice([upgrade])
    out = capsys.readouterr().out
    assert "firefox" in out
    assert "pinned" in out.lower()
    assert "1" in out


def test_pinned_notice_multiple_pinned_packages(capsys) -> None:
    v = _make_view_for_pinned()
    v.utils.is_package_installed.return_value = "pkg-1.0-x86_64-1.txz"
    upgrade = _make_upgrade_for_pinned(upgradeable=True)
    with patch("slpkg.views.display.Pin") as MockPin:
        MockPin.return_value.read_pins.return_value = {"firefox", "thunderbird"}
        v._pinned_notice([upgrade])
    out = capsys.readouterr().out
    assert "firefox" in out
    assert "thunderbird" in out
    assert "2" in out


def test_pinned_notice_multi_repo_checks_all_upgrades(capsys) -> None:
    v = _make_view_for_pinned()
    v.utils.is_package_installed.return_value = "firefox-130.0-x86_64-1alien.txz"
    upgrade_a = _make_upgrade_for_pinned(upgradeable=False)
    upgrade_b = _make_upgrade_for_pinned(upgradeable=True)
    with patch("slpkg.views.display.Pin") as MockPin:
        MockPin.return_value.read_pins.return_value = {"firefox"}
        v._pinned_notice([upgrade_a, upgrade_b])
    out = capsys.readouterr().out
    assert "firefox" in out


def test_pinned_notice_defaults_to_self_upgrade(capsys) -> None:
    v = _make_view_for_pinned()
    v.utils.is_package_installed.return_value = "firefox-130.0-x86_64-1alien.txz"
    v.upgrade = _make_upgrade_for_pinned(upgradeable=True)
    with patch("slpkg.views.display.Pin") as MockPin:
        MockPin.return_value.read_pins.return_value = {"firefox"}
        v._pinned_notice()
    assert "firefox" in capsys.readouterr().out


# ---------------------------------------------------------------------------
# make_view_ext — extended helper for imprint/summary/question tests
# ---------------------------------------------------------------------------


def make_view_ext() -> View:
    """Extend make_view_full() with attributes needed by imprint/summary/question methods."""
    v = make_view_full()
    v.sum_remove = 0
    v.sum_size_remove = 0
    v.mode = ""
    v.repository = "alien"
    v.option_for_reinstall = False
    v.ask_question = True
    v.answer_yes = False

    mock_upgrade = MagicMock(spec=Upgrade)
    mock_upgrade.is_package_upgradeable.return_value = False
    v.upgrade = mock_upgrade

    v.data = {}
    return v


# ---------------------------------------------------------------------------
# summary(package)
# ---------------------------------------------------------------------------


def test_summary_not_installed_increments_sum_install() -> None:
    v = make_view_ext()
    v.mode = "install"
    v.utils.is_package_installed.return_value = ""
    v.summary("ffmpeg")
    assert v.sum_install == 1
    assert v.sum_upgrade == 0


def test_summary_installed_upgradeable_increments_sum_upgrade() -> None:
    v = make_view_ext()
    v.mode = "upgrade"
    v.utils.is_package_installed.return_value = "ffmpeg-7.0-x86_64-1alien.txz"
    v.upgrade.is_package_upgradeable.return_value = True
    v.summary("ffmpeg")
    assert v.sum_upgrade == 1
    assert v.sum_install == 0


def test_summary_installed_not_upgradeable_no_count_change() -> None:
    v = make_view_ext()
    v.mode = "upgrade"
    v.utils.is_package_installed.return_value = "ffmpeg-7.1-x86_64-1alien.txz"
    v.upgrade.is_package_upgradeable.return_value = False
    v.summary("ffmpeg")
    assert v.sum_install == 0
    assert v.sum_upgrade == 0


def test_summary_remove_mode_increments_sum_remove() -> None:
    v = make_view_ext()
    v.mode = "remove"
    v.utils.is_package_installed.return_value = "ffmpeg-7.1-x86_64-1alien.txz"
    v.utils.count_file_size.return_value = 1024
    v.summary("ffmpeg")
    assert v.sum_remove == 1


def test_summary_remove_mode_accumulates_size() -> None:
    v = make_view_ext()
    v.mode = "remove"
    v.utils.is_package_installed.return_value = "ffmpeg-7.1-x86_64-1alien.txz"
    v.utils.count_file_size.return_value = 2048
    v.summary("ffmpeg")
    assert v.sum_size_remove == 2048


def test_summary_reinstall_option_increments_sum_upgrade() -> None:
    v = make_view_ext()
    v.mode = "upgrade"
    v.option_for_reinstall = True
    v.utils.is_package_installed.return_value = "ffmpeg-7.1-x86_64-1alien.txz"
    v.upgrade.is_package_upgradeable.return_value = False
    v.summary("ffmpeg")
    assert v.sum_upgrade == 1


def test_summary_binary_repo_accumulates_sizes() -> None:
    v = make_view_ext()
    v.mode = "install"
    v.repository = "alien"
    v.repos.sbo_repo_name = "sbo"
    v.repos.ponce_repo_name = "ponce"
    v.data = {"ffmpeg": {"size_comp": "10", "size_uncomp": "20"}}
    v.utils.is_package_installed.return_value = ""
    v.summary("ffmpeg")
    assert v.sum_size_comp == 10 * 1024
    assert v.sum_size_uncomp == 20 * 1024


def test_summary_sbo_repo_no_sizes_accumulated() -> None:
    v = make_view_ext()
    v.mode = "install"
    v.repository = "sbo"
    v.repos.sbo_repo_name = "sbo"
    v.repos.ponce_repo_name = "ponce"
    v.data = {"vmpk": {"size_comp": "50", "size_uncomp": "100"}}
    v.utils.is_package_installed.return_value = ""
    v.summary("vmpk")
    assert v.sum_size_comp == 0.0
    assert v.sum_size_uncomp == 0.0


# ---------------------------------------------------------------------------
# build_package_and_version(package)
# ---------------------------------------------------------------------------


def test_build_package_and_version_installed() -> None:
    v = make_view_ext()
    v.utils.is_package_installed.return_value = "ffmpeg-7.0-x86_64-1alien.txz"
    v.utils.split_package.return_value = {"version": "7.0"}
    result = v.build_package_and_version("ffmpeg")
    assert result == "ffmpeg-7.0"


def test_build_package_and_version_not_installed() -> None:
    v = make_view_ext()
    v.utils.is_package_installed.return_value = ""
    result = v.build_package_and_version("ffmpeg")
    assert result == "ffmpeg-N/A"


# ---------------------------------------------------------------------------
# skipping_packages(packages)
# ---------------------------------------------------------------------------


def test_skipping_packages_prints_skipped_name(capsys) -> None:
    v = make_view_ext()
    v.imp.skipped = "SKIP"
    v.data = {"ffmpeg": {"package": "ffmpeg-7.1-x86_64-1alien.txz"}}
    v.skipping_packages(["ffmpeg"])
    out = capsys.readouterr().out
    assert "skipped" in out.lower()
    assert "ffmpeg" in out


def test_skipping_packages_empty_list_prints_nothing(capsys) -> None:
    v = make_view_ext()
    v.imp.skipped = "SKIP"
    v.data = {}
    v.skipping_packages([])
    assert capsys.readouterr().out == ""


def test_skipping_packages_fallback_to_name_when_no_package_key(capsys) -> None:
    v = make_view_ext()
    v.imp.skipped = "SKIP"
    v.data = {}  # No entry for "mypkg"
    v.skipping_packages(["mypkg"])
    out = capsys.readouterr().out
    assert "mypkg" in out


# ---------------------------------------------------------------------------
# question(message)
# ---------------------------------------------------------------------------


def test_question_yes_answer_does_not_raise(capsys) -> None:
    v = make_view_ext()
    v.ask_question = True
    v.answer_yes = False
    with patch("builtins.input", return_value="y"):
        v.question()  # Must not raise


def test_question_no_answer_raises_systemexit_0(capsys) -> None:
    v = make_view_ext()
    v.ask_question = True
    v.answer_yes = False
    with patch("builtins.input", return_value="n"):
        with pytest.raises(SystemExit) as exc:
            v.question()
    assert exc.value.code == 0


def test_question_auto_yes_does_not_raise(capsys) -> None:
    v = make_view_ext()
    v.ask_question = True
    v.answer_yes = True
    v.question()  # Must not raise


def test_question_disabled_does_not_raise(capsys) -> None:
    v = make_view_ext()
    v.ask_question = False
    v.question()  # Must not raise


def test_question_keyboard_interrupt_raises_systemexit_1(capsys) -> None:
    v = make_view_ext()
    v.ask_question = True
    v.answer_yes = False
    with patch("builtins.input", side_effect=KeyboardInterrupt):
        with pytest.raises(SystemExit) as exc:
            v.question()
    assert exc.value.code == 1


def test_question_custom_message_used(capsys) -> None:
    v = make_view_ext()
    v.ask_question = True
    v.answer_yes = False
    with patch("builtins.input", return_value="y") as mock_input:
        v.question("Proceed with upgrade?")
    call_args = mock_input.call_args[0][0]
    assert "Proceed with upgrade?" in call_args


# ---------------------------------------------------------------------------
# imprint_build_package(package)
# ---------------------------------------------------------------------------


def test_imprint_build_package_calls_package_line() -> None:
    v = make_view_ext()
    v.data = {"ffmpeg": {"version": "7.1"}}
    v.imprint_build_package("ffmpeg")
    v.imp.package_line.assert_called_once()


def test_imprint_build_package_uses_version_from_data() -> None:
    v = make_view_ext()
    v.data = {"ffmpeg": {"version": "7.1"}}
    v.imprint_build_package("ffmpeg")
    call_arg = v.imp.package_line.call_args[0][0]
    assert call_arg.version == "7.1"


def test_imprint_build_package_missing_from_data_uses_na() -> None:
    v = make_view_ext()
    v.data = {}
    v.imprint_build_package("ffmpeg")
    call_arg = v.imp.package_line.call_args[0][0]
    assert call_arg.version == "N/A"


# ---------------------------------------------------------------------------
# imprint_install_upgrade_package(package)
# ---------------------------------------------------------------------------


def test_imprint_install_upgrade_not_installed_uses_green() -> None:
    v = make_view_ext()
    v.green = "GREEN"
    v.data = {"ffmpeg": {"version": "7.1", "size_comp": "0"}}
    v.utils.is_package_installed.return_value = ""
    v.upgrade.is_package_upgradeable.return_value = False
    v.imprint_install_upgrade_package("ffmpeg")
    call_arg = v.imp.package_line.call_args[0][0]
    assert call_arg.color == "GREEN"


def test_imprint_install_upgrade_installed_not_upgradeable_uses_grey() -> None:
    v = make_view_ext()
    v.grey = "GREY"
    v.data = {"ffmpeg": {"version": "7.1", "size_comp": "0"}}
    v.utils.is_package_installed.return_value = "ffmpeg-7.1-x86_64-1alien.txz"
    v.upgrade.is_package_upgradeable.return_value = False
    v.imprint_install_upgrade_package("ffmpeg")
    call_arg = v.imp.package_line.call_args[0][0]
    assert call_arg.color == "GREY"


def test_imprint_install_upgrade_upgradeable_uses_yellow() -> None:
    v = make_view_ext()
    v.yellow = "YELLOW"
    v.data = {"ffmpeg": {"version": "7.1", "size_comp": "0"}}
    v.utils.is_package_installed.return_value = "ffmpeg-7.0-x86_64-1alien.txz"
    v.utils.split_package.return_value = {"version": "7.0"}
    v.upgrade.is_package_upgradeable.return_value = True
    v.imprint_install_upgrade_package("ffmpeg")
    call_arg = v.imp.package_line.call_args[0][0]
    assert call_arg.color == "YELLOW"


def test_imprint_install_upgrade_sbo_repo_no_size() -> None:
    v = make_view_ext()
    v.repository = "sbo"
    v.repos.sbo_repo_name = "sbo"
    v.repos.ponce_repo_name = "ponce"
    v.data = {"vmpk": {"version": "0.9", "size_comp": "50"}}
    v.utils.is_package_installed.return_value = ""
    v.upgrade.is_package_upgradeable.return_value = False
    v.imprint_install_upgrade_package("vmpk")
    call_arg = v.imp.package_line.call_args[0][0]
    assert call_arg.size == ""


# ---------------------------------------------------------------------------
# imprint_download_package(package)
# ---------------------------------------------------------------------------


def test_imprint_download_package_calls_package_line() -> None:
    v = make_view_ext()
    v.data = {"ffmpeg": {"version": "7.1", "size_comp": "10"}}
    v.imprint_download_package("ffmpeg")
    v.imp.package_line.assert_called_once()


def test_imprint_download_package_sbo_repo_no_size() -> None:
    v = make_view_ext()
    v.repository = "sbo"
    v.repos.sbo_repo_name = "sbo"
    v.repos.ponce_repo_name = "ponce"
    v.data = {"vmpk": {"version": "0.9", "size_comp": "50"}}
    v.imprint_download_package("vmpk")
    call_arg = v.imp.package_line.call_args[0][0]
    assert call_arg.size == ""


def test_imprint_download_package_uses_green_color() -> None:
    v = make_view_ext()
    v.green = "GREEN"
    v.data = {"ffmpeg": {"version": "7.1", "size_comp": "0"}}
    v.imprint_download_package("ffmpeg")
    call_arg = v.imp.package_line.call_args[0][0]
    assert call_arg.color == "GREEN"


# ---------------------------------------------------------------------------
# imprint_remove_package(package)
# ---------------------------------------------------------------------------


def test_imprint_remove_package_calls_package_line() -> None:
    v = make_view_ext()
    v.utils.is_package_installed.return_value = "ffmpeg-7.1-x86_64-1alien.txz"
    v.utils.split_package.return_value = {"version": "7.1", "tag": "alien"}
    v.utils.count_file_size.return_value = 0
    v.imprint_remove_package("ffmpeg")
    v.imp.package_line.assert_called_once()


def test_imprint_remove_package_not_installed_uses_na() -> None:
    v = make_view_ext()
    v.utils.is_package_installed.return_value = ""
    v.utils.count_file_size.return_value = 0
    v.imprint_remove_package("ffmpeg")
    call_arg = v.imp.package_line.call_args[0][0]
    assert call_arg.version == "N/A"


def test_imprint_remove_package_uses_red_color() -> None:
    v = make_view_ext()
    v.red = "RED"
    v.utils.is_package_installed.return_value = ""
    v.utils.count_file_size.return_value = 0
    v.imprint_remove_package("ffmpeg")
    call_arg = v.imp.package_line.call_args[0][0]
    assert call_arg.color == "RED"


# ---------------------------------------------------------------------------
# build_packages / install_upgrade_packages / download_packages / remove_packages
# High-level methods: verify mode is set and summary is printed
# ---------------------------------------------------------------------------


def _make_full_view() -> View:
    """Make a view ready for high-level method calls (mocks all sub-calls)."""
    v = make_view_ext()
    v.utils.is_package_installed.return_value = ""
    v.utils.count_file_size.return_value = 0
    v.utils.split_package.return_value = {"version": "1.0", "tag": "alien"}
    return v


def test_build_packages_sets_mode_build(capsys) -> None:
    v = _make_full_view()
    v.data = {"ffmpeg": {"version": "7.1"}}
    v.build_packages(["ffmpeg"], [])
    assert v.mode == "build"


def test_build_packages_prints_summary(capsys) -> None:
    v = _make_full_view()
    v.data = {"ffmpeg": {"version": "7.1"}}
    v.build_packages(["ffmpeg"], [])
    out = capsys.readouterr().out
    assert "summary" in out.lower()


def test_build_packages_with_deps_calls_dependency_status(capsys) -> None:
    v = _make_full_view()
    v.data = {
        "ffmpeg": {"version": "7.1"},
        "libvpx": {"version": "1.13"},
    }
    v.build_packages(["ffmpeg"], ["libvpx"])
    v.imp.dependency_status.assert_called_once()


def test_install_upgrade_packages_install_mode(capsys) -> None:
    v = _make_full_view()
    v.data = {"ffmpeg": {"version": "7.1", "size_comp": "0", "size_uncomp": "0"}}
    v.install_upgrade_packages(["ffmpeg"], [], "install")
    assert v.mode == "install"
    v.imp.package_status.assert_called_once_with("Installing:")


def test_install_upgrade_packages_upgrade_mode(capsys) -> None:
    v = _make_full_view()
    v.data = {"ffmpeg": {"version": "7.1", "size_comp": "0", "size_uncomp": "0"}}
    with patch("slpkg.views.display.Pin") as MockPin:
        MockPin.return_value.read_pins.return_value = set()
        v.install_upgrade_packages(["ffmpeg"], [], "upgrade")
    assert v.mode == "upgrade"


def test_install_upgrade_packages_reinstall_mode(capsys) -> None:
    v = _make_full_view()
    v.data = {"ffmpeg": {"version": "7.1", "size_comp": "0", "size_uncomp": "0"}}
    v.install_upgrade_packages(["ffmpeg"], [], "reinstall")
    assert v.mode == "reinstall"
    v.imp.package_status.assert_called_once_with("Reinstalling:")


def test_install_upgrade_packages_with_deps(capsys) -> None:
    v = _make_full_view()
    v.data = {
        "ffmpeg": {"version": "7.1", "size_comp": "0", "size_uncomp": "0"},
        "libvpx": {"version": "1.13", "size_comp": "0", "size_uncomp": "0"},
    }
    v.install_upgrade_packages(["ffmpeg"], ["libvpx"], "install")
    v.imp.dependency_status.assert_called_once()


def test_download_packages_sets_mode_download(capsys) -> None:
    v = _make_full_view()
    v.data = {"ffmpeg": {"version": "7.1", "size_comp": "0"}}
    v.download_packages(["ffmpeg"], Path("/tmp/dl"))
    assert v.mode == "download"


def test_download_packages_prints_summary(capsys) -> None:
    v = _make_full_view()
    v.data = {"ffmpeg": {"version": "7.1", "size_comp": "0"}}
    v.download_packages(["ffmpeg"], Path("/tmp/dl"))
    out = capsys.readouterr().out
    assert "summary" in out.lower()


def test_remove_packages_sets_mode_remove(capsys) -> None:
    v = _make_full_view()
    v.remove_packages(["ffmpeg"], [])
    assert v.mode == "remove"


def test_remove_packages_prints_summary(capsys) -> None:
    v = _make_full_view()
    v.remove_packages(["ffmpeg"], [])
    out = capsys.readouterr().out
    assert "summary" in out.lower()


def test_remove_packages_with_deps_shows_dependency_status(capsys) -> None:
    v = _make_full_view()
    v.remove_packages(["ffmpeg"], ["libvpx"])
    v.imp.dependency_status.assert_called_once()
