from __future__ import annotations

from unittest import mock

import pytest

import pre_commit.constants as C
from pre_commit import parse_shebang
from pre_commit.languages import rust
from pre_commit.store import _make_local_repo
from testing.language_helpers import run_language
from testing.util import cwd

ACTUAL_GET_DEFAULT_VERSION = rust.get_default_version.__wrapped__


@pytest.fixture
def cmd_output_b_mck():
    with mock.patch.object(rust, 'cmd_output_b') as mck:
        yield mck


def test_sets_system_when_rust_is_available(cmd_output_b_mck):
    cmd_output_b_mck.return_value = (0, b'', b'')
    assert ACTUAL_GET_DEFAULT_VERSION() == 'system'


def test_uses_default_when_rust_is_not_available(cmd_output_b_mck):
    cmd_output_b_mck.return_value = (127, b'', b'error: not found')
    assert ACTUAL_GET_DEFAULT_VERSION() == C.DEFAULT


def test_selects_system_even_if_rust_toolchain_toml(tmp_path):
    toolchain_toml = '[toolchain]\nchannel = "wtf"\n'
    tmp_path.joinpath('rust-toolchain.toml').write_text(toolchain_toml)

    with cwd(tmp_path):
        assert ACTUAL_GET_DEFAULT_VERSION() == 'system'


def _make_hello_world(tmp_path):
    src_dir = tmp_path.joinpath('src')
    src_dir.mkdir()
    src_dir.joinpath('main.rs').write_text(
        'fn main() {\n'
        '    println!("Hello, world!");\n'
        '}\n',
    )
    tmp_path.joinpath('Cargo.toml').write_text(
        '[package]\n'
        'name = "hello_world"\n'
        'version = "0.1.0"\n'
        'edition = "2021"\n',
    )


def test_installs_rust_missing_rustup(tmp_path):
    _make_hello_world(tmp_path)

    # pretend like `rustup` doesn't exist so it gets bootstrapped
    calls = []
    orig = parse_shebang.find_executable

    def mck(exe, env=None):
        calls.append(exe)
        if len(calls) == 1:
            assert exe == 'rustup'
            return None
        return orig(exe, env=env)

    with mock.patch.object(parse_shebang, 'find_executable', side_effect=mck):
        ret = run_language(tmp_path, rust, 'hello_world', version='1.56.0')
    assert calls == ['rustup', 'rustup', 'cargo', 'hello_world']
    assert ret == (0, b'Hello, world!\n')


@pytest.mark.parametrize('version', (C.DEFAULT, '1.56.0'))
def test_language_version_with_rustup(tmp_path, version):
    assert parse_shebang.find_executable('rustup') is not None

    _make_hello_world(tmp_path)

    ret = run_language(tmp_path, rust, 'hello_world', version=version)
    assert ret == (0, b'Hello, world!\n')


@pytest.mark.parametrize('dep', ('cli:shellharden:4.2.0', 'cli:shellharden'))
def test_rust_cli_additional_dependencies(tmp_path, dep):
    _make_local_repo(str(tmp_path))

    t_sh = tmp_path.joinpath('t.sh')
    t_sh.write_text('echo $hi\n')

    assert rust.get_default_version() == 'system'
    ret = run_language(
        tmp_path,
        rust,
        'shellharden --transform',
        deps=(dep,),
        args=(str(t_sh),),
    )
    assert ret == (0, b'echo "$hi"\n')


def test_run_lib_additional_dependencies(tmp_path):
    _make_hello_world(tmp_path)

    deps = ('shellharden:4.2.0', 'git-version')
    ret = run_language(tmp_path, rust, 'hello_world', deps=deps)
    assert ret == (0, b'Hello, world!\n')

    bin_dir = tmp_path.joinpath('rustenv-system', 'bin')
    assert bin_dir.is_dir()
    assert not bin_dir.joinpath('shellharden').exists()
    assert not bin_dir.joinpath('shellharden.exe').exists()
