import subprocess
import tempfile
from .exceptions import ErrorInterrogatingEnvironment, MalformedVersionInput, MissingDependency, OutdatedDependency
from .conf import settings
from .utils import six


def run_command(cmd_to_run):
    """
    Wrapper around subprocess that pipes the stderr and stdout from `cmd_to_run`
    to temporary files. Using the temporary files gets around subprocess.PIPE's
    issues with handling large buffers.

    Note: this command will block the python process until `cmd_to_run` has completed.

    Returns a tuple, containing the stderr and stdout as strings.
    """
    with tempfile.TemporaryFile() as stdout_file, tempfile.TemporaryFile() as stderr_file:

        # Run the command
        popen = subprocess.Popen(cmd_to_run, stdout=stdout_file, stderr=stderr_file)
        popen.wait()

        stderr_file.seek(0)
        stdout_file.seek(0)

        stderr = stderr_file.read()
        stdout = stdout_file.read()

        if six.PY3:
            stderr = stderr.decode()
            stdout = stdout.decode()

        return stderr, stdout


try:
    _stderr, _stdout = run_command((settings.PATH, settings.VERSION_COMMAND,))
    if _stderr:
        raise ErrorInterrogatingEnvironment(_stderr)
    npm_installed = True
    npm_version_raw = _stdout.strip()
except OSError:
    npm_installed = False
    npm_version_raw = None
npm_version = None
if npm_version_raw:
    npm_version = settings.VERSION_FILTER(npm_version_raw)


def _check_if_version_is_outdated(current_version, required_version):
    if not isinstance(required_version, tuple):
        raise MalformedVersionInput(
            'Versions must be tuples. Received {0}'.format(required_version)
        )
    if len(required_version) < 3:
        raise MalformedVersionInput(
            'Versions must have three numbers defined. Received {0}'.format(required_version)
        )
    for number in required_version:
        if not isinstance(number, six.integer_types):
            raise MalformedVersionInput(
                'Versions can only contain number. Received {0}'.format(required_version)
            )
    for i, number_required in enumerate(required_version):
        if number_required > current_version[i]:
            return True
        elif number_required < current_version[i]:
            return False
    return current_version != required_version


def _format_version(version):
    return '.'.join(map(six.text_type, version))


def raise_if_npm_missing(required_version=None):
    if not npm_installed:
        error = 'npm is not installed or cannot be found at path "{path}"'.format(path=settings.PATH)
        if required_version:
            error += '. Version {required_version} or greater is required.'.format(
                required_version=_format_version(required_version)
            )
        raise MissingDependency(error)


def raise_if_npm_version_less_than(required_version):
    if _check_if_version_is_outdated(npm_version, required_version):
        raise OutdatedDependency(
            (
                'The installed npm version is outdated. Version {current_version} is installed, but version '
                '{required_version} is required. Please update npm.'
            ).format(
                current_version=_format_version(npm_version),
                required_version=_format_version(required_version),
            )
        )