import sys
from dataclasses import replace

import pytest

from helpers import PIPX_METADATA_LEGACY_VERSIONS, app_name, mock_legacy_venv, run_pipx_cli, skip_if_windows
from pipx import paths, util
from pipx.pipx_metadata_file import PipxMetadata


def test_reinstall(pipx_temp_env, capsys):
    assert not run_pipx_cli(["install", "pycowsay"])
    assert not run_pipx_cli(["reinstall", "--python", sys.executable, "pycowsay"])


@skip_if_windows
def test_reinstall_global(pipx_temp_env, capsys):
    assert not run_pipx_cli(["install", "--global", "pycowsay"])
    assert not run_pipx_cli(["reinstall", "--global", "--python", sys.executable, "pycowsay"])


def test_reinstall_nonexistent(pipx_temp_env, capsys):
    assert run_pipx_cli(["reinstall", "--python", sys.executable, "nonexistent"])
    assert "Nothing to reinstall for nonexistent" in capsys.readouterr().out


@pytest.mark.parametrize("metadata_version", PIPX_METADATA_LEGACY_VERSIONS)
def test_reinstall_legacy_venv(pipx_temp_env, capsys, metadata_version):
    assert not run_pipx_cli(["install", "pycowsay"])
    mock_legacy_venv("pycowsay", metadata_version=metadata_version)

    assert not run_pipx_cli(["reinstall", "--python", sys.executable, "pycowsay"])


def test_reinstall_suffix(pipx_temp_env, capsys):
    suffix = "_x"
    assert not run_pipx_cli(["install", "pycowsay", f"--suffix={suffix}"])

    assert not run_pipx_cli(["reinstall", "--python", sys.executable, f"pycowsay{suffix}"])


@pytest.mark.parametrize("metadata_version", ["0.1"])
def test_reinstall_suffix_legacy_venv(pipx_temp_env, capsys, metadata_version):
    suffix = "_x"
    assert not run_pipx_cli(["install", "pycowsay", f"--suffix={suffix}"])
    mock_legacy_venv(f"pycowsay{suffix}", metadata_version=metadata_version)

    assert not run_pipx_cli(["reinstall", "--python", sys.executable, f"pycowsay{suffix}"])


def test_reinstall_specifier(pipx_temp_env, capsys):
    assert not run_pipx_cli(["install", "pylint==3.0.4"])

    # clear capsys before reinstall
    captured = capsys.readouterr()

    assert not run_pipx_cli(["reinstall", "--python", sys.executable, "pylint"])
    captured = capsys.readouterr()
    assert "installed package pylint 3.0.4" in captured.out


@skip_if_windows
def test_reinstall_removes_stale_apps_after_success(pipx_temp_env, capsys):
    assert not run_pipx_cli(["install", "pycowsay"])
    capsys.readouterr()

    venv_dir = paths.ctx.venvs / "pycowsay"
    stale_app_name = app_name("removed-cowsay")
    stale_app_path = util.get_venv_paths(venv_dir)[0] / stale_app_name
    stale_app_path.write_text("#!/bin/sh\n")
    stale_app_path.chmod(0o755)

    stale_exposed_path = paths.ctx.bin_dir / stale_app_name
    stale_exposed_path.symlink_to(stale_app_path)

    metadata = PipxMetadata(venv_dir)
    metadata.main_package = replace(
        metadata.main_package,
        apps=[*metadata.main_package.apps, stale_app_name],
        app_paths=[*metadata.main_package.app_paths, stale_app_path],
    )
    metadata.write()

    assert not run_pipx_cli(["reinstall", "--python", sys.executable, "pycowsay"])

    assert not stale_exposed_path.exists()
    assert not stale_exposed_path.is_symlink()


def test_reinstall_with_path(pipx_temp_env, capsys, tmp_path):
    path = tmp_path / "some" / "path"

    assert run_pipx_cli(["reinstall", str(path)])
    captured = capsys.readouterr()

    assert "Expected the name of an installed package" in captured.err.replace("\n", " ")

    assert run_pipx_cli(["reinstall", str(path.resolve())])
    captured = capsys.readouterr()

    assert "Expected the name of an installed package" in captured.err.replace("\n", " ")


def test_reinstall_pinned_package(pipx_temp_env, capsys):
    assert not run_pipx_cli(["install", "black"])
    assert not run_pipx_cli(["pin", "black"])
    assert run_pipx_cli(["reinstall", "black"])
    captured = capsys.readouterr()
    assert "pinned" in captured.err

    assert not run_pipx_cli(["unpin", "black"])
    assert not run_pipx_cli(["reinstall", "black"])
    captured = capsys.readouterr()
    assert "installed package black" in captured.out
