# -*- coding: utf-8 -*-
import os
import subprocess

import pytest

from cookiecutter import exceptions, vcs


@pytest.fixture
def clone_dir(tmpdir):
    """Simulates creation of a directory called `clone_dir` inside of `tmpdir`.
    Returns a str to said directory.
    """
    return str(tmpdir.mkdir('clone_dir'))


def test_clone_should_raise_if_vcs_not_installed(mocker, clone_dir):
    """In `clone()`, a `VCSNotInstalled` exception should be raised if no VCS
    is installed.
    """
    mocker.patch(
        'cookiecutter.vcs.is_vcs_installed',
        autospec=True,
        return_value=False
    )

    repo_url = 'https://github.com/pytest-dev/cookiecutter-pytest-plugin.git'

    with pytest.raises(exceptions.VCSNotInstalled):
        vcs.clone(repo_url, clone_to_dir=clone_dir)


def test_clone_should_rstrip_trailing_slash_in_repo_url(mocker, clone_dir):
    """In `clone()`, repo URL's trailing slash should be stripped if one is
    present.
    """
    mocker.patch(
        'cookiecutter.vcs.is_vcs_installed',
        autospec=True,
        return_value=True
    )

    mock_subprocess = mocker.patch(
        'cookiecutter.vcs.subprocess.check_output',
        autospec=True,
    )

    vcs.clone(
        'https://github.com/foo/bar/',
        clone_to_dir=clone_dir,
        no_input=True
    )

    mock_subprocess.assert_called_once_with(
        ['git', 'clone', 'https://github.com/foo/bar'],
        cwd=clone_dir,
        stderr=subprocess.STDOUT
    )


def test_clone_should_abort_if_user_does_not_want_to_reclone(mocker, tmpdir):
    """In `clone()`, if user doesn't want to reclone, Cookiecutter should exit
    without cloning anything.
    """
    mocker.patch(
        'cookiecutter.vcs.is_vcs_installed',
        autospec=True,
        return_value=True
    )
    mocker.patch(
        'cookiecutter.vcs.prompt_and_delete',
        side_effect=SystemExit,
        autospec=True
    )
    mock_subprocess = mocker.patch(
        'cookiecutter.vcs.subprocess.check_output',
        autospec=True,
    )

    clone_to_dir = tmpdir.mkdir('clone')

    # Create repo_dir to trigger prompt_and_delete
    clone_to_dir.mkdir('cookiecutter-pytest-plugin')

    repo_url = 'https://github.com/pytest-dev/cookiecutter-pytest-plugin.git'

    with pytest.raises(SystemExit):
        vcs.clone(repo_url, clone_to_dir=str(clone_to_dir))
    assert not mock_subprocess.called


@pytest.mark.parametrize('repo_type, repo_url, repo_name', [
    ('git', 'https://github.com/hello/world.git', 'world'),
    ('hg', 'https://bitbucket.org/foo/bar', 'bar'),
])
def test_clone_should_invoke_vcs_command(
        mocker, clone_dir, repo_type, repo_url, repo_name):
    """When `clone()` is called with a git/hg repo, the corresponding VCS
    command should be run via `subprocess.check_output()`.

    This should take place:
    * In the correct dir
    * With the correct args.
    """
    mocker.patch(
        'cookiecutter.vcs.is_vcs_installed',
        autospec=True,
        return_value=True
    )

    mock_subprocess = mocker.patch(
        'cookiecutter.vcs.subprocess.check_output',
        autospec=True,
    )
    expected_repo_dir = os.path.normpath(os.path.join(clone_dir, repo_name))

    branch = 'foobar'

    repo_dir = vcs.clone(
        repo_url,
        checkout=branch,
        clone_to_dir=clone_dir,
        no_input=True
    )

    assert repo_dir == expected_repo_dir

    mock_subprocess.assert_any_call(
        [repo_type, 'clone', repo_url],
        cwd=clone_dir,
        stderr=subprocess.STDOUT
    )
    mock_subprocess.assert_any_call(
        [repo_type, 'checkout', branch],
        cwd=expected_repo_dir,
        stderr=subprocess.STDOUT
    )


@pytest.mark.parametrize('error_message', [
    (
        "fatal: repository 'https://github.com/hackebro/cookiedozer' "
        "not found"
    ).encode('utf-8'),
    'hg: abort: HTTP Error 404: Not Found'.encode('utf-8'),
])
def test_clone_handles_repo_typo(mocker, clone_dir, error_message):
    """In `clone()`, repository not found errors should raise an
    appropriate exception.
    """
    # side_effect is set to an iterable here (and below),
    # because of a Python 3.4 unittest.mock regression
    # http://bugs.python.org/issue23661
    mocker.patch(
        'cookiecutter.vcs.subprocess.check_output',
        autospec=True,
        side_effect=[subprocess.CalledProcessError(
            -1, 'cmd', output=error_message
        )]
    )

    repository_url = 'https://github.com/hackebro/cookiedozer'
    with pytest.raises(exceptions.RepositoryNotFound) as err:
        vcs.clone(
            repository_url,
            clone_to_dir=clone_dir,
            no_input=True
        )

    assert str(err.value) == (
        'The repository {} could not be found, have you made a typo?'
    ).format(repository_url)


@pytest.mark.parametrize('error_message', [
    (
        "error: pathspec 'unknown_branch' did not match any file(s) known "
        "to git"
    ).encode('utf-8'),
    "hg: abort: unknown revision 'unknown_branch'!".encode('utf-8'),
])
def test_clone_handles_branch_typo(mocker, clone_dir, error_message):
    """In `clone()`, branch not found errors should raise an
    appropriate exception.
    """
    mocker.patch(
        'cookiecutter.vcs.subprocess.check_output',
        autospec=True,
        side_effect=[subprocess.CalledProcessError(
            -1, 'cmd', output=error_message
        )]
    )

    repository_url = 'https://github.com/pytest-dev/cookiecutter-pytest-plugin'
    with pytest.raises(exceptions.RepositoryCloneFailed) as err:
        vcs.clone(
            repository_url,
            clone_to_dir=clone_dir,
            checkout='unknown_branch',
            no_input=True
        )

    assert str(err.value) == (
        'The unknown_branch branch of repository '
        '{} could not found, have you made a typo?'
    ).format(repository_url)


def test_clone_unknown_subprocess_error(mocker, clone_dir):
    """In `clone()`, unknown subprocess errors should be raised."""
    mocker.patch(
        'cookiecutter.vcs.subprocess.check_output',
        autospec=True,
        side_effect=[subprocess.CalledProcessError(
            -1, 'cmd', output='Something went wrong'.encode('utf-8')
        )]
    )

    with pytest.raises(subprocess.CalledProcessError):
        vcs.clone(
            'https://github.com/pytest-dev/cookiecutter-pytest-plugin',
            clone_to_dir=clone_dir,
            no_input=True
        )
