import multiprocessing
import os
import random
import re
from typing import Any
from typing import List
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING

import pre_commit.constants as C
from pre_commit import parse_shebang
from pre_commit.hook import Hook
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output_b
from pre_commit.xargs import xargs

if TYPE_CHECKING:
    from typing import NoReturn

FIXED_RANDOM_SEED = 1542676187

SHIMS_RE = re.compile(r'[/\\]shims[/\\]')


def exe_exists(exe: str) -> bool:
    found = parse_shebang.find_executable(exe)
    if found is None:  # exe exists
        return False

    homedir = os.path.expanduser('~')
    try:
        common: Optional[str] = os.path.commonpath((found, homedir))
    except ValueError:  # on windows, different drives raises ValueError
        common = None

    return (
        # it is not in a /shims/ directory
        not SHIMS_RE.search(found) and
        (
            # the homedir is / (docker, service user, etc.)
            os.path.dirname(homedir) == homedir or
            # the exe is not contained in the home directory
            common != homedir
        )
    )


def run_setup_cmd(prefix: Prefix, cmd: Tuple[str, ...]) -> None:
    cmd_output_b(*cmd, cwd=prefix.prefix_dir)


@overload
def environment_dir(d: None, language_version: str) -> None: ...
@overload
def environment_dir(d: str, language_version: str) -> str: ...


def environment_dir(d: Optional[str], language_version: str) -> Optional[str]:
    if d is None:
        return None
    else:
        return f'{d}-{language_version}'


def assert_version_default(binary: str, version: str) -> None:
    if version != C.DEFAULT:
        raise AssertionError(
            f'For now, pre-commit requires system-installed {binary}',
        )


def assert_no_additional_deps(
        lang: str,
        additional_deps: Sequence[str],
) -> None:
    if additional_deps:
        raise AssertionError(
            f'For now, pre-commit does not support '
            f'additional_dependencies for {lang}',
        )


def basic_get_default_version() -> str:
    return C.DEFAULT


def basic_healthy(prefix: Prefix, language_version: str) -> bool:
    return True


def no_install(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> 'NoReturn':
    raise AssertionError('This type is not installable')


def target_concurrency(hook: Hook) -> int:
    if hook.require_serial or 'PRE_COMMIT_NO_CONCURRENCY' in os.environ:
        return 1
    else:
        # Travis appears to have a bunch of CPUs, but we can't use them all.
        if 'TRAVIS' in os.environ:
            return 2
        else:
            try:
                return multiprocessing.cpu_count()
            except NotImplementedError:
                return 1


def _shuffled(seq: Sequence[str]) -> List[str]:
    """Deterministically shuffle"""
    fixed_random = random.Random()
    fixed_random.seed(FIXED_RANDOM_SEED, version=1)

    seq = list(seq)
    fixed_random.shuffle(seq)
    return seq


def run_xargs(
        hook: Hook,
        cmd: Tuple[str, ...],
        file_args: Sequence[str],
        **kwargs: Any,
) -> Tuple[int, bytes]:
    # Shuffle the files so that they more evenly fill out the xargs partitions,
    # but do it deterministically in case a hook cares about ordering.
    file_args = _shuffled(file_args)
    kwargs['target_concurrency'] = target_concurrency(hook)
    return xargs(cmd, file_args, **kwargs)
