#!/usr/bin/python3
"""Tests for slpkg.local_install.LocalInstall."""

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

import pytest

from slpkg.local_install import LocalInstall


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


def make_local_install(
    tmp_path: Path,
    packages: list[str],
    *,
    yes: bool = False,
    reinstall: bool = False,
    extra_args: str = "",
) -> LocalInstall:
    """Construct LocalInstall bypassing __init__ (avoids config_load)."""
    obj: LocalInstall = object.__new__(LocalInstall)
    obj.packages = [Path(p) for p in packages]
    obj.options = {
        "option_yes": yes,
        "option_reinstall": reinstall,
        "option_extra_args": extra_args,
    }
    obj.option_yes = yes
    obj.option_reinstall = reinstall
    obj.option_extra_args = extra_args

    obj.installpkg = "/sbin/upgradepkg --install-new"
    obj.reinstall = "/sbin/upgradepkg --reinstall"
    obj.package_type = (".tgz", ".txz", ".tbz", ".tlz")
    obj.process_log_file = tmp_path / "process.log"
    obj.green = ""
    obj.yellow = ""
    obj.red = ""
    obj.endc = ""

    obj.utils = MagicMock()
    obj.utils.split_package.side_effect = lambda stem: {"name": stem.rsplit("-", 3)[0]}
    obj.utils.is_package_installed.return_value = ""

    obj.pkg_validator = MagicMock()
    obj.pkg_validator.get_blacklist_status.return_value = ""

    obj.multi_proc = MagicMock()
    obj.multi_proc.process_and_log.return_value = True

    obj.history_logger = MagicMock()
    return obj


def make_pkg(tmp_path: Path, name: str = "ffmpeg-6.1-x86_64-1_SBo.txz") -> Path:
    p = tmp_path / name
    p.write_bytes(b"dummy")
    return p


# ---------------------------------------------------------------------------
# _validate
# ---------------------------------------------------------------------------


def test_validate_accepts_valid_txz(tmp_path: Path) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)])
    result = obj._validate()
    assert pkg in result


def test_validate_rejects_missing_file(tmp_path: Path, capsys) -> None:
    obj = make_local_install(tmp_path, [str(tmp_path / "missing.txz")])
    result = obj._validate()
    assert result == []
    assert "File not found" in capsys.readouterr().out


def test_validate_rejects_wrong_extension(tmp_path: Path, capsys) -> None:
    bad = tmp_path / "ffmpeg.rpm"
    bad.write_bytes(b"dummy")
    obj = make_local_install(tmp_path, [str(bad)])
    result = obj._validate()
    assert result == []
    assert "Not a valid" in capsys.readouterr().out


def test_validate_accepts_multiple_valid_packages(tmp_path: Path) -> None:
    pkgs = [make_pkg(tmp_path, f"pkg{i}-1.0-x86_64-1.txz") for i in range(3)]
    obj = make_local_install(tmp_path, [str(p) for p in pkgs])
    result = obj._validate()
    assert len(result) == 3


def test_validate_skips_invalid_keeps_valid(tmp_path: Path, capsys) -> None:
    good = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(tmp_path / "missing.txz"), str(good)])
    result = obj._validate()
    assert good in result
    assert len(result) == 1


# ---------------------------------------------------------------------------
# _show_packages
# ---------------------------------------------------------------------------


def test_show_packages_prints_install_for_new(tmp_path: Path, capsys) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)])
    obj.utils.is_package_installed.return_value = ""
    obj._show_packages([pkg])
    out = capsys.readouterr().out
    assert "install" in out
    assert pkg.name in out


def test_show_packages_prints_upgrade_for_installed(tmp_path: Path, capsys) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)])
    obj.utils.is_package_installed.return_value = "ffmpeg-6.0-x86_64-1_SBo"
    obj._show_packages([pkg])
    out = capsys.readouterr().out
    assert "upgrade" in out


def test_show_packages_prints_reinstall_when_option_set(tmp_path: Path, capsys) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)], reinstall=True)
    obj._show_packages([pkg])
    out = capsys.readouterr().out
    assert "reinstall" in out


def test_show_packages_warns_when_blacklisted(tmp_path: Path, capsys) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)])
    obj.pkg_validator.get_blacklist_status.return_value = " [blacklisted]"
    obj._show_packages([pkg])
    out = capsys.readouterr().out
    assert "blacklist" in out.lower()


# ---------------------------------------------------------------------------
# run — no valid packages
# ---------------------------------------------------------------------------


def test_run_does_nothing_when_no_valid_packages(tmp_path: Path) -> None:
    obj = make_local_install(tmp_path, [str(tmp_path / "missing.txz")])
    obj.run()
    obj.multi_proc.process_and_log.assert_not_called()


# ---------------------------------------------------------------------------
# run — user declines prompt
# ---------------------------------------------------------------------------


def test_run_aborts_when_user_says_no(tmp_path: Path) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)], yes=False)
    with patch("builtins.input", return_value="n"):
        obj.run()
    obj.multi_proc.process_and_log.assert_not_called()


def test_run_aborts_on_keyboard_interrupt(tmp_path: Path) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)], yes=False)
    with patch("builtins.input", side_effect=KeyboardInterrupt):
        obj.run()
    obj.multi_proc.process_and_log.assert_not_called()


# ---------------------------------------------------------------------------
# run — installs
# ---------------------------------------------------------------------------


def test_run_installs_with_yes_option(tmp_path: Path) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)], yes=True)
    with patch("shutil.get_terminal_size") as mock_ts:
        mock_ts.return_value.columns = 80
        obj.run()
    obj.multi_proc.process_and_log.assert_called_once()


def test_run_logs_history_on_success(tmp_path: Path) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)], yes=True)
    obj.multi_proc.process_and_log.return_value = True
    with patch("shutil.get_terminal_size") as mock_ts:
        mock_ts.return_value.columns = 80
        obj.run()
    obj.history_logger.write.assert_called_once()


def test_run_skips_history_on_failure(tmp_path: Path) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)], yes=True)
    obj.multi_proc.process_and_log.return_value = False
    with patch("shutil.get_terminal_size") as mock_ts:
        mock_ts.return_value.columns = 80
        obj.run()
    obj.history_logger.write.assert_not_called()


def test_run_uses_reinstall_command_when_option_set(tmp_path: Path) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)], yes=True, reinstall=True)
    with patch("shutil.get_terminal_size") as mock_ts:
        mock_ts.return_value.columns = 80
        obj.run()
    call_args = obj.multi_proc.process_and_log.call_args[0]
    assert "--reinstall" in call_args[0]


def test_run_includes_extra_args_in_command(tmp_path: Path) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)], yes=True, extra_args="--terse")
    with patch("shutil.get_terminal_size") as mock_ts:
        mock_ts.return_value.columns = 80
        obj.run()
    call_args = obj.multi_proc.process_and_log.call_args[0]
    assert "--terse" in call_args[0]


def test_run_calls_finished_time(tmp_path: Path) -> None:
    pkg = make_pkg(tmp_path)
    obj = make_local_install(tmp_path, [str(pkg)], yes=True)
    with patch("shutil.get_terminal_size") as mock_ts:
        mock_ts.return_value.columns = 80
        obj.run()
    obj.utils.finished_time.assert_called_once()
