from __future__ import annotations

from typing import Mapping
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.prefix import Prefix
from pre_commit.util import cmd_output

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


@pytest.mark.parametrize('language_version', (C.DEFAULT, '1.56.0'))
def test_installs_with_bootstrapped_rustup(tmpdir, language_version):
    tmpdir.join('src', 'main.rs').ensure().write(
        'fn main() {\n'
        '    println!("Hello, world!");\n'
        '}\n',
    )
    tmpdir.join('Cargo.toml').ensure().write(
        '[package]\n'
        'name = "hello_world"\n'
        'version = "0.1.0"\n'
        'edition = "2021"\n',
    )
    prefix = Prefix(str(tmpdir))

    find_executable_exes = []

    original_find_executable = parse_shebang.find_executable

    def mocked_find_executable(
            exe: str, *, env: Mapping[str, str] | None = None,
    ) -> str | None:
        """
        Return `None` the first time `find_executable` is called to ensure
        that the bootstrapping code is executed, then just let the function
        work as normal.

        Also log the arguments to ensure that everything works as expected.
        """
        find_executable_exes.append(exe)
        if len(find_executable_exes) == 1:
            return None
        return original_find_executable(exe, env=env)

    with mock.patch.object(parse_shebang, 'find_executable') as find_exe_mck:
        find_exe_mck.side_effect = mocked_find_executable
        rust.install_environment(prefix, language_version, ())
        assert find_executable_exes == ['rustup', 'rustup', 'cargo']

    with rust.in_env(prefix, language_version):
        assert cmd_output('hello_world')[1] == 'Hello, world!\n'


def test_installs_with_existing_rustup(tmpdir):
    tmpdir.join('src', 'main.rs').ensure().write(
        'fn main() {\n'
        '    println!("Hello, world!");\n'
        '}\n',
    )
    tmpdir.join('Cargo.toml').ensure().write(
        '[package]\n'
        'name = "hello_world"\n'
        'version = "0.1.0"\n'
        'edition = "2021"\n',
    )
    prefix = Prefix(str(tmpdir))

    assert parse_shebang.find_executable('rustup') is not None
    rust.install_environment(prefix, '1.56.0', ())
    with rust.in_env(prefix, '1.56.0'):
        assert cmd_output('hello_world')[1] == 'Hello, world!\n'
