import stat
import sys
import pytest
import signal
from pathlib import Path
from textwrap import dedent
from testlib.fixtures.env import ManagedEnv
from testlib.fixtures.nix import Nix

COMMANDS = ["copy-closure", "collect-garbage"]


@pytest.fixture(scope="module")
def custom_sub_command_path(tmp_path_factory: pytest.TempPathFactory) -> Path:
    return tmp_path_factory.mktemp(__name__)


@pytest.fixture(scope="module")
def failing_sub_command_path(tmp_path_factory: pytest.TempPathFactory) -> Path:
    return tmp_path_factory.mktemp(__name__ + "-failing")


@pytest.fixture(scope="module", params=COMMANDS)
def custom_sub_command(request: pytest.FixtureRequest, custom_sub_command_path: Path) -> str:
    # Create an external command that is an alias of an existing one
    lix_cmd = request.param

    executable = custom_sub_command_path / f"lix-{lix_cmd}"
    executable.write_text(
        dedent(f"""\
         #!{sys.executable}
         import os, sys
         # Start with args[0] set to the actual nix command used for testing
         # as we are not making Lix variants of those.
         os.execvp("nix", [ "nix-{lix_cmd}" ] + sys.argv[1:])
         """)
    )
    executable.chmod(stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)

    return lix_cmd


@pytest.fixture(scope="module")
def failing_sub_command(failing_sub_command_path: Path, custom_sub_command: str) -> str:
    # Create an external command that will intentionally cause an error
    executable = failing_sub_command_path / f"lix-{custom_sub_command}"
    executable.write_text(
        dedent(f"""\
         #!{sys.executable}
         import sys
         sys.exit(42)
         """)
    )
    executable.chmod(stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)

    return custom_sub_command


@pytest.fixture
def path(custom_sub_command_path: Path, env: ManagedEnv):
    """Provide a modified search path"""
    env.path.append("/some/incorrect")
    env.path.append("/location/for/fun")
    env.path.append(str(custom_sub_command_path))


@pytest.fixture
def path_with_failure(
    request: pytest.FixtureRequest,
    custom_sub_command_path: Path,
    failing_sub_command_path: Path,
    env: ManagedEnv,
):
    """
    Provide a search path with failing binaries inserted in the specified position
    """
    (first, fail) = request.param

    command_path = [str(custom_sub_command_path), str(failing_sub_command_path)]
    if fail:
        command_path.reverse()

    if first:
        env.path.prepend(command_path[1])
        env.path.prepend(command_path[0])
    else:
        env.path.append(command_path[0])
        env.path.append(command_path[1])


@pytest.mark.parametrize(
    ("nix_exe", "flag", "expected"),
    [("nix", False, 1), ("lix", False, 1), ("nix", True, 1), ("lix", True, 0)],
)
@pytest.mark.usefixtures("path")
def test_sub_commands(nix: Nix, custom_sub_command: str, nix_exe: str, flag: bool, expected: int):
    if flag:
        nix.settings.add_xp_feature("lix-custom-sub-commands")

    # Test custom sub commands in various configurations
    nix.nix([custom_sub_command, "--version"], nix_exe=nix_exe).run().expect(expected)


@pytest.mark.parametrize(
    ("path_with_failure", "expected"),
    [((True, True), 42), ((True, False), 0), ((False, True), 42), ((False, False), 0)],
    indirect=["path_with_failure"],
)
@pytest.mark.usefixtures("path_with_failure")
def test_sub_command_path_order(nix: Nix, failing_sub_command: str, expected: int):
    # Test handling of the order of the path for custom sub commands
    # Incidentally also tests passing through exit codes
    nix.settings.add_xp_feature("lix-custom-sub-commands")
    nix.nix([failing_sub_command, "--version"], nix_exe="lix").run().expect(expected)


@pytest.mark.skip(
    reason="TODO(raito): we do not support auto completion for custom sub commands for now"
)
def test_custom_sub_commands_auto_completion(nix: Nix, tmp_path: Path): ...


@pytest.mark.skip(
    reason="TODO(raito): we do not test flag handling for custom sub commands for now"
)
def test_custom_sub_command_flag_handling(nix: Nix, tmp_path: Path):
    # Short, long and multiple flags should be tested as well.
    # `--` special flag
    # test positional arguments, but only `nix-copy-closure` implements some, and it's pesky to test here.
    ...


@pytest.mark.usefixtures("path")
def test_custom_sub_command_signals(nix: Nix, custom_sub_command_path: Path):
    command = custom_sub_command_path / "lix-signals"
    command.write_text(
        dedent(f"""\
            #!{sys.executable}
            import signal
            print(signal.pthread_sigmask(signal.SIG_BLOCK, []))
        """)
    )
    command.chmod(stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)

    current_mask = signal.pthread_sigmask(signal.SIG_BLOCK, [])
    nix.settings.add_xp_feature("lix-custom-sub-commands")
    result = nix.nix(["signals"], nix_exe="lix").run()
    result.ok()
    assert result.stdout_s.strip() == str(current_mask)
