import os.path
import shutil
import textwrap

import pytest

from pip._internal.cli.status_codes import ERROR
from tests.lib.path import Path


def fake_wheel(data, wheel_path):
    shutil.copy(
        data.packages.joinpath('simple.dist-0.1-py2.py3-none-any.whl'),
        data.packages.joinpath(wheel_path),
    )


@pytest.mark.network
def test_download_if_requested(script):
    """
    It should download (in the scratch path) and not install if requested.
    """
    result = script.pip(
        'download', '-d', 'pip_downloads', 'INITools==0.1'
    )
    assert Path('scratch') / 'pip_downloads' / 'INITools-0.1.tar.gz' \
        in result.files_created
    assert script.site_packages / 'initools' not in result.files_created


@pytest.mark.network
def test_basic_download_setuptools(script):
    """
    It should download (in the scratch path) and not install if requested.
    """
    result = script.pip('download', 'setuptools')
    setuptools_prefix = str(Path('scratch') / 'setuptools')
    assert any(
        path.startswith(setuptools_prefix) for path in result.files_created
    )


def test_download_wheel(script, data):
    """
    Test using "pip download" to download a *.whl archive.
    """
    result = script.pip(
        'download',
        '--no-index',
        '-f', data.packages,
        '-d', '.', 'meta'
    )
    assert (
        Path('scratch') / 'meta-1.0-py2.py3-none-any.whl'
        in result.files_created
    )
    assert script.site_packages / 'piptestpackage' not in result.files_created


@pytest.mark.network
def test_single_download_from_requirements_file(script):
    """
    It should support download (in the scratch path) from PyPI from a
    requirements file
    """
    script.scratch_path.joinpath("test-req.txt").write_text(textwrap.dedent("""
        INITools==0.1
        """))
    result = script.pip(
        'download', '-r', script.scratch_path / 'test-req.txt', '-d', '.',
    )
    assert Path('scratch') / 'INITools-0.1.tar.gz' in result.files_created
    assert script.site_packages / 'initools' not in result.files_created


@pytest.mark.network
def test_basic_download_should_download_dependencies(script):
    """
    It should download dependencies (in the scratch path)
    """
    result = script.pip(
        'download', 'Paste[openid]==1.7.5.1', '-d', '.'
    )
    assert Path('scratch') / 'Paste-1.7.5.1.tar.gz' in result.files_created
    openid_tarball_prefix = str(Path('scratch') / 'python-openid-')
    assert any(
        path.startswith(openid_tarball_prefix) for path in result.files_created
    )
    assert script.site_packages / 'openid' not in result.files_created


def test_download_wheel_archive(script, data):
    """
    It should download a wheel archive path
    """
    wheel_filename = 'colander-0.9.9-py2.py3-none-any.whl'
    wheel_path = '/'.join((data.find_links, wheel_filename))
    result = script.pip(
        'download', wheel_path,
        '-d', '.', '--no-deps'
    )
    assert Path('scratch') / wheel_filename in result.files_created


def test_download_should_download_wheel_deps(script, data):
    """
    It should download dependencies for wheels(in the scratch path)
    """
    wheel_filename = 'colander-0.9.9-py2.py3-none-any.whl'
    dep_filename = 'translationstring-1.1.tar.gz'
    wheel_path = '/'.join((data.find_links, wheel_filename))
    result = script.pip(
        'download', wheel_path,
        '-d', '.', '--find-links', data.find_links, '--no-index'
    )
    assert Path('scratch') / wheel_filename in result.files_created
    assert Path('scratch') / dep_filename in result.files_created


@pytest.mark.network
def test_download_should_skip_existing_files(script):
    """
    It should not download files already existing in the scratch dir
    """
    script.scratch_path.joinpath("test-req.txt").write_text(textwrap.dedent("""
        INITools==0.1
        """))

    result = script.pip(
        'download', '-r', script.scratch_path / 'test-req.txt', '-d', '.',
    )
    assert Path('scratch') / 'INITools-0.1.tar.gz' in result.files_created
    assert script.site_packages / 'initools' not in result.files_created

    # adding second package to test-req.txt
    script.scratch_path.joinpath("test-req.txt").write_text(textwrap.dedent("""
        INITools==0.1
        python-openid==2.2.5
        """))

    # only the second package should be downloaded
    result = script.pip(
        'download', '-r', script.scratch_path / 'test-req.txt', '-d', '.',
    )
    openid_tarball_prefix = str(Path('scratch') / 'python-openid-')
    assert any(
        path.startswith(openid_tarball_prefix) for path in result.files_created
    )
    assert Path('scratch') / 'INITools-0.1.tar.gz' not in result.files_created
    assert script.site_packages / 'initools' not in result.files_created
    assert script.site_packages / 'openid' not in result.files_created


@pytest.mark.network
def test_download_vcs_link(script):
    """
    It should allow -d flag for vcs links, regression test for issue #798.
    """
    result = script.pip(
        'download', '-d', '.', 'git+git://github.com/pypa/pip-test-package.git'
    )
    assert (
        Path('scratch') / 'pip-test-package-0.1.1.zip'
        in result.files_created
    )
    assert script.site_packages / 'piptestpackage' not in result.files_created


def test_only_binary_set_then_download_specific_platform(script, data):
    """
    Confirm that specifying an interpreter/platform constraint
    is allowed when ``--only-binary=:all:`` is set.
    """
    fake_wheel(data, 'fake-1.0-py2.py3-none-any.whl')

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--platform', 'linux_x86_64',
        'fake'
    )
    assert (
        Path('scratch') / 'fake-1.0-py2.py3-none-any.whl'
        in result.files_created
    )


def test_no_deps_set_then_download_specific_platform(script, data):
    """
    Confirm that specifying an interpreter/platform constraint
    is allowed when ``--no-deps`` is set.
    """
    fake_wheel(data, 'fake-1.0-py2.py3-none-any.whl')

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--no-deps',
        '--dest', '.',
        '--platform', 'linux_x86_64',
        'fake'
    )
    assert (
        Path('scratch') / 'fake-1.0-py2.py3-none-any.whl'
        in result.files_created
    )


def test_download_specific_platform_fails(script, data):
    """
    Confirm that specifying an interpreter/platform constraint
    enforces that ``--no-deps`` or ``--only-binary=:all:`` is set.
    """
    fake_wheel(data, 'fake-1.0-py2.py3-none-any.whl')

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--dest', '.',
        '--platform', 'linux_x86_64',
        'fake',
        expect_error=True,
    )
    assert '--only-binary=:all:' in result.stderr


def test_no_binary_set_then_download_specific_platform_fails(script, data):
    """
    Confirm that specifying an interpreter/platform constraint
    enforces that ``--only-binary=:all:`` is set without ``--no-binary``.
    """
    fake_wheel(data, 'fake-1.0-py2.py3-none-any.whl')

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--no-binary=fake',
        '--dest', '.',
        '--platform', 'linux_x86_64',
        'fake',
        expect_error=True,
    )
    assert '--only-binary=:all:' in result.stderr


def test_download_specify_platform(script, data):
    """
    Test using "pip download --platform" to download a .whl archive
    supported for a specific platform
    """
    fake_wheel(data, 'fake-1.0-py2.py3-none-any.whl')

    # Confirm that universal wheels are returned even for specific
    # platforms.
    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--platform', 'linux_x86_64',
        'fake'
    )
    assert (
        Path('scratch') / 'fake-1.0-py2.py3-none-any.whl'
        in result.files_created
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--platform', 'macosx_10_9_x86_64',
        'fake'
    )

    data.reset()
    fake_wheel(data, 'fake-1.0-py2.py3-none-macosx_10_9_x86_64.whl')
    fake_wheel(data, 'fake-2.0-py2.py3-none-linux_x86_64.whl')

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--platform', 'macosx_10_10_x86_64',
        'fake'
    )
    assert (
        Path('scratch') /
        'fake-1.0-py2.py3-none-macosx_10_9_x86_64.whl'
        in result.files_created
    )

    # OSX platform wheels are not backward-compatible.
    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--platform', 'macosx_10_8_x86_64',
        'fake',
        expect_error=True,
    )

    # No linux wheel provided for this version.
    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--platform', 'linux_x86_64',
        'fake==1',
        expect_error=True,
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--platform', 'linux_x86_64',
        'fake==2'
    )
    assert (
        Path('scratch') / 'fake-2.0-py2.py3-none-linux_x86_64.whl'
        in result.files_created
    )


class TestDownloadPlatformManylinuxes(object):
    """
    "pip download --platform" downloads a .whl archive supported for
    manylinux platforms.
    """

    @pytest.mark.parametrize("platform", [
        "linux_x86_64",
        "manylinux1_x86_64",
        "manylinux2010_x86_64",
        "manylinux2014_x86_64",
    ])
    def test_download_universal(self, platform, script, data):
        """
        Universal wheels are returned even for specific platforms.
        """
        fake_wheel(data, 'fake-1.0-py2.py3-none-any.whl')
        result = script.pip(
            'download', '--no-index', '--find-links', data.find_links,
            '--only-binary=:all:',
            '--dest', '.',
            '--platform', platform,
            'fake',
        )
        assert (
            Path('scratch') / 'fake-1.0-py2.py3-none-any.whl'
            in result.files_created
        )

    @pytest.mark.parametrize("wheel_abi,platform", [
        ("manylinux1_x86_64", "manylinux1_x86_64"),
        ("manylinux1_x86_64", "manylinux2010_x86_64"),
        ("manylinux2010_x86_64", "manylinux2010_x86_64"),
        ("manylinux1_x86_64", "manylinux2014_x86_64"),
        ("manylinux2010_x86_64", "manylinux2014_x86_64"),
        ("manylinux2014_x86_64", "manylinux2014_x86_64"),
    ])
    def test_download_compatible_manylinuxes(
            self, wheel_abi, platform, script, data,
    ):
        """
        Earlier manylinuxes are compatible with later manylinuxes.
        """
        wheel = 'fake-1.0-py2.py3-none-{}.whl'.format(wheel_abi)
        fake_wheel(data, wheel)
        result = script.pip(
            'download', '--no-index', '--find-links', data.find_links,
            '--only-binary=:all:',
            '--dest', '.',
            '--platform', platform,
            'fake',
        )
        assert Path('scratch') / wheel in result.files_created

    def test_explicit_platform_only(self, data, script):
        """
        When specifying the platform, manylinux1 needs to be the
        explicit platform--it won't ever be added to the compatible
        tags.
        """
        fake_wheel(data, 'fake-1.0-py2.py3-none-linux_x86_64.whl')
        script.pip(
            'download', '--no-index', '--find-links', data.find_links,
            '--only-binary=:all:',
            '--dest', '.',
            '--platform', 'linux_x86_64',
            'fake',
        )


def test_download__python_version(script, data):
    """
    Test using "pip download --python-version" to download a .whl archive
    supported for a specific interpreter
    """
    fake_wheel(data, 'fake-1.0-py2.py3-none-any.whl')
    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--python-version', '2',
        'fake'
    )
    assert (
        Path('scratch') / 'fake-1.0-py2.py3-none-any.whl'
        in result.files_created
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--python-version', '3',
        'fake'
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--python-version', '27',
        'fake'
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--python-version', '33',
        'fake'
    )

    data.reset()
    fake_wheel(data, 'fake-1.0-py2-none-any.whl')
    fake_wheel(data, 'fake-2.0-py3-none-any.whl')

    # No py3 provided for version 1.
    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--python-version', '3',
        'fake==1.0',
        expect_error=True,
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--python-version', '2',
        'fake'
    )
    assert (
        Path('scratch') / 'fake-1.0-py2-none-any.whl'
        in result.files_created
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--python-version', '26',
        'fake'
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--python-version', '3',
        'fake'
    )
    assert (
        Path('scratch') / 'fake-2.0-py3-none-any.whl'
        in result.files_created
    )


def make_wheel_with_python_requires(script, package_name, python_requires):
    """
    Create a wheel using the given python_requires.

    :return: the path to the wheel file.
    """
    package_dir = script.scratch_path / package_name
    package_dir.mkdir()

    text = textwrap.dedent("""\
    from setuptools import setup
    setup(name='{}',
          python_requires='{}',
          version='1.0')
    """).format(package_name, python_requires)
    package_dir.joinpath('setup.py').write_text(text)
    script.run(
        'python', 'setup.py', 'bdist_wheel', '--universal', cwd=package_dir,
    )

    file_name = '{}-1.0-py2.py3-none-any.whl'.format(package_name)
    return package_dir / 'dist' / file_name


def test_download__python_version_used_for_python_requires(
    script, data, with_wheel,
):
    """
    Test that --python-version is used for the Requires-Python check.
    """
    wheel_path = make_wheel_with_python_requires(
        script, 'mypackage', python_requires='==3.2',
    )
    wheel_dir = os.path.dirname(wheel_path)

    def make_args(python_version):
        return [
            'download', '--no-index', '--find-links', wheel_dir,
            '--only-binary=:all:',
            '--dest', '.',
            '--python-version', python_version,
            'mypackage==1.0',
        ]

    args = make_args('33')
    result = script.pip(*args, expect_error=True)
    expected_err = (
        "ERROR: Package 'mypackage' requires a different Python: "
        "3.3.0 not in '==3.2'"
    )
    assert expected_err in result.stderr, 'stderr: {}'.format(result.stderr)

    # Now try with a --python-version that satisfies the Requires-Python.
    args = make_args('32')
    script.pip(*args)  # no exception


def test_download_specify_abi(script, data):
    """
    Test using "pip download --abi" to download a .whl archive
    supported for a specific abi
    """
    fake_wheel(data, 'fake-1.0-py2.py3-none-any.whl')
    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--implementation', 'fk',
        '--abi', 'fake_abi',
        'fake'
    )
    assert (
        Path('scratch') / 'fake-1.0-py2.py3-none-any.whl'
        in result.files_created
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--implementation', 'fk',
        '--abi', 'none',
        'fake'
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--abi', 'cp27m',
        'fake',
    )

    data.reset()
    fake_wheel(data, 'fake-1.0-fk2-fakeabi-fake_platform.whl')
    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--python-version', '2',
        '--implementation', 'fk',
        '--platform', 'fake_platform',
        '--abi', 'fakeabi',
        'fake'
    )
    assert (
        Path('scratch') / 'fake-1.0-fk2-fakeabi-fake_platform.whl'
        in result.files_created
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--implementation', 'fk',
        '--platform', 'fake_platform',
        '--abi', 'none',
        'fake',
        expect_error=True,
    )


def test_download_specify_implementation(script, data):
    """
    Test using "pip download --abi" to download a .whl archive
    supported for a specific abi
    """
    fake_wheel(data, 'fake-1.0-py2.py3-none-any.whl')
    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--implementation', 'fk',
        'fake'
    )
    assert (
        Path('scratch') / 'fake-1.0-py2.py3-none-any.whl'
        in result.files_created
    )

    data.reset()
    fake_wheel(data, 'fake-1.0-fk2.fk3-none-any.whl')
    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--implementation', 'fk',
        'fake'
    )
    assert (
        Path('scratch') / 'fake-1.0-fk2.fk3-none-any.whl'
        in result.files_created
    )

    data.reset()
    fake_wheel(data, 'fake-1.0-fk3-none-any.whl')
    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--implementation', 'fk',
        '--python-version', '3',
        'fake'
    )
    assert (
        Path('scratch') / 'fake-1.0-fk3-none-any.whl'
        in result.files_created
    )

    result = script.pip(
        'download', '--no-index', '--find-links', data.find_links,
        '--only-binary=:all:',
        '--dest', '.',
        '--implementation', 'fk',
        '--python-version', '2',
        'fake',
        expect_error=True,
    )


def test_download_exit_status_code_when_no_requirements(script):
    """
    Test download exit status code when no requirements specified
    """
    result = script.pip('download', expect_error=True)
    assert (
        "You must give at least one requirement to download" in result.stderr
    )
    assert result.returncode == ERROR


def test_download_exit_status_code_when_blank_requirements_file(script):
    """
    Test download exit status code when blank requirements file specified
    """
    script.scratch_path.joinpath("blank.txt").write_text("\n")
    script.pip('download', '-r', 'blank.txt')


def test_download_prefer_binary_when_tarball_higher_than_wheel(script, data):
    fake_wheel(data, 'source-0.8-py2.py3-none-any.whl')
    result = script.pip(
        'download',
        '--prefer-binary',
        '--no-index',
        '-f', data.packages,
        '-d', '.', 'source'
    )
    assert (
        Path('scratch') / 'source-0.8-py2.py3-none-any.whl'
        in result.files_created
    )
    assert (
        Path('scratch') / 'source-1.0.tar.gz'
        not in result.files_created
    )


def test_download_prefer_binary_when_wheel_doesnt_satisfy_req(script, data):
    fake_wheel(data, 'source-0.8-py2.py3-none-any.whl')
    script.scratch_path.joinpath("test-req.txt").write_text(textwrap.dedent("""
        source>0.9
        """))

    result = script.pip(
        'download',
        '--prefer-binary',
        '--no-index',
        '-f', data.packages,
        '-d', '.',
        '-r', script.scratch_path / 'test-req.txt'
    )
    assert (
        Path('scratch') / 'source-1.0.tar.gz'
        in result.files_created
    )
    assert (
        Path('scratch') / 'source-0.8-py2.py3-none-any.whl'
        not in result.files_created
    )


def test_download_prefer_binary_when_only_tarball_exists(script, data):
    result = script.pip(
        'download',
        '--prefer-binary',
        '--no-index',
        '-f', data.packages,
        '-d', '.', 'source'
    )
    assert (
        Path('scratch') / 'source-1.0.tar.gz'
        in result.files_created
    )
