import os
import textwrap

import pytest

from tests.lib import (
    _create_test_package_with_subdirectory,
    need_svn,
    path_to_url,
    pyversion,
    requirements_file,
)
from tests.lib.local_repos import local_checkout
from tests.lib.path import Path


@pytest.mark.network
def test_requirements_file(script):
    """
    Test installing from a requirements file.

    """
    other_lib_name, other_lib_version = 'anyjson', '0.3'
    script.scratch_path.joinpath("initools-req.txt").write_text(textwrap.dedent("""\
        INITools==0.2
        # and something else to test out:
        {other_lib_name}<={other_lib_version}
        """.format(**locals())))
    result = script.pip(
        'install', '-r', script.scratch_path / 'initools-req.txt'
    )
    assert (
        script.site_packages / 'INITools-0.2-py{}.egg-info'.format(
            pyversion) in result.files_created
    )
    assert script.site_packages / 'initools' in result.files_created
    assert result.files_created[script.site_packages / other_lib_name].dir
    fn = '{}-{}-py{}.egg-info'.format(
        other_lib_name, other_lib_version, pyversion)
    assert result.files_created[script.site_packages / fn].dir


def test_schema_check_in_requirements_file(script):
    """
    Test installing from a requirements file with an invalid vcs schema..

    """
    script.scratch_path.joinpath("file-egg-req.txt").write_text(
        "\n{}\n".format(
            "git://github.com/alex/django-fixture-generator.git"
            "#egg=fixture_generator"
        )
    )

    with pytest.raises(AssertionError):
        script.pip(
            "install", "-vvv", "-r", script.scratch_path / "file-egg-req.txt"
        )


@pytest.mark.parametrize("test_type,editable", [
    ("rel_path", False),
    ("rel_path", True),
    ("rel_url", False),
    ("rel_url", True),
    ("embedded_rel_path", False),
    ("embedded_rel_path", True),
])
def test_relative_requirements_file(script, data, test_type, editable):
    """
    Test installing from a requirements file with a relative path. For path
    URLs, use an egg= definition.

    """
    egg_info_file = (
        script.site_packages /
        'FSPkg-0.1.dev0-py{pyversion}.egg-info'.format(**globals())
    )
    egg_link_file = (
        script.site_packages / 'FSPkg.egg-link'
    )
    package_folder = script.site_packages / 'fspkg'

    # Compute relative install path to FSPkg from scratch path.
    full_rel_path = Path(
        os.path.relpath(data.packages.joinpath('FSPkg'), script.scratch_path)
    )
    full_rel_url = 'file:' + full_rel_path + '#egg=FSPkg'
    embedded_rel_path = script.scratch_path.joinpath(full_rel_path)

    req_path = {
        "rel_path": full_rel_path,
        "rel_url": full_rel_url,
        "embedded_rel_path": embedded_rel_path,
    }[test_type]

    req_path = req_path.replace(os.path.sep, '/')
    # Install as either editable or not.
    if not editable:
        with requirements_file(req_path + '\n',
                               script.scratch_path) as reqs_file:
            result = script.pip('install', '-vvv', '-r', reqs_file.name,
                                cwd=script.scratch_path)
            assert egg_info_file in result.files_created, str(result)
            assert package_folder in result.files_created, str(result)
    else:
        with requirements_file('-e ' + req_path + '\n',
                               script.scratch_path) as reqs_file:
            result = script.pip('install', '-vvv', '-r', reqs_file.name,
                                cwd=script.scratch_path)
            assert egg_link_file in result.files_created, str(result)


@pytest.mark.network
@need_svn
def test_multiple_requirements_files(script, tmpdir):
    """
    Test installing from multiple nested requirements files.

    """
    other_lib_name, other_lib_version = 'anyjson', '0.3'
    script.scratch_path.joinpath("initools-req.txt").write_text(
        textwrap.dedent("""
            -e {}@10#egg=INITools
            -r {}-req.txt
        """).format
        (
            local_checkout('svn+http://svn.colorstudy.com/INITools', tmpdir),
            other_lib_name
        ),
    )
    script.scratch_path.joinpath(
        "{other_lib_name}-req.txt".format(**locals())).write_text(
            "{other_lib_name}<={other_lib_version}".format(**locals())
    )
    result = script.pip(
        'install', '-r', script.scratch_path / 'initools-req.txt'
    )
    assert result.files_created[script.site_packages / other_lib_name].dir
    fn = '{other_lib_name}-{other_lib_version}-py{pyversion}.egg-info'.format(
        pyversion=pyversion, **locals())
    assert result.files_created[script.site_packages / fn].dir
    assert script.venv / 'src' / 'initools' in result.files_created


def test_package_in_constraints_and_dependencies(script, data):
    script.scratch_path.joinpath("constraints.txt").write_text(
        "TopoRequires2==0.0.1\nTopoRequires==0.0.1"
    )
    result = script.pip('install', '--no-index', '-f',
                        data.find_links, '-c', script.scratch_path /
                        'constraints.txt', 'TopoRequires2')
    assert 'installed TopoRequires-0.0.1' in result.stdout


def test_multiple_constraints_files(script, data):
    script.scratch_path.joinpath("outer.txt").write_text("-c inner.txt")
    script.scratch_path.joinpath("inner.txt").write_text(
        "Upper==1.0")
    result = script.pip(
        'install', '--no-index', '-f', data.find_links, '-c',
        script.scratch_path / 'outer.txt', 'Upper')
    assert 'installed Upper-1.0' in result.stdout


def test_respect_order_in_requirements_file(script, data):
    script.scratch_path.joinpath("frameworks-req.txt").write_text(textwrap.dedent("""\
        parent
        child
        simple
        """))

    result = script.pip(
        'install', '--no-index', '-f', data.find_links, '-r',
        script.scratch_path / 'frameworks-req.txt'
    )

    downloaded = [line for line in result.stdout.split('\n')
                  if 'Processing' in line]

    assert 'parent' in downloaded[0], (
        'First download should be "parent" but was "{}"'.format(downloaded[0])
    )
    assert 'child' in downloaded[1], (
        'Second download should be "child" but was "{}"'.format(downloaded[1])
    )
    assert 'simple' in downloaded[2], (
        'Third download should be "simple" but was "{}"'.format(downloaded[2])
    )


def test_install_local_editable_with_extras(script, data):
    to_install = data.packages.joinpath("LocalExtras")
    res = script.pip_install_local(
        '-e', to_install + '[bar]', allow_stderr_warning=True
    )
    assert script.site_packages / 'easy-install.pth' in res.files_updated, (
        str(res)
    )
    assert (
        script.site_packages / 'LocalExtras.egg-link' in res.files_created
    ), str(res)
    assert script.site_packages / 'simple' in res.files_created, str(res)


def test_install_collected_dependencies_first(script):
    result = script.pip_install_local(
        'toporequires2',
    )
    text = [line for line in result.stdout.split('\n')
            if 'Installing' in line][0]
    assert text.endswith('toporequires2')


@pytest.mark.network
def test_install_local_editable_with_subdirectory(script):
    version_pkg_path = _create_test_package_with_subdirectory(script,
                                                              'version_subdir')
    result = script.pip(
        'install', '-e',
        '{uri}#egg=version_subpkg&subdirectory=version_subdir'.format(
            uri='git+' + path_to_url(version_pkg_path),
        ),
    )

    result.assert_installed('version-subpkg', sub_dir='version_subdir')


@pytest.mark.network
def test_install_local_with_subdirectory(script):
    version_pkg_path = _create_test_package_with_subdirectory(script,
                                                              'version_subdir')
    result = script.pip(
        'install',
        '{uri}#egg=version_subpkg&subdirectory=version_subdir'.format(
            uri='git+' + path_to_url(version_pkg_path),
        ),
    )

    result.assert_installed('version_subpkg.py', editable=False)


@pytest.mark.incompatible_with_test_venv
def test_wheel_user_with_prefix_in_pydistutils_cfg(
        script, data, with_wheel):
    if os.name == 'posix':
        user_filename = ".pydistutils.cfg"
    else:
        user_filename = "pydistutils.cfg"
    user_cfg = os.path.join(os.path.expanduser('~'), user_filename)
    script.scratch_path.joinpath("bin").mkdir()
    with open(user_cfg, "w") as cfg:
        cfg.write(textwrap.dedent("""
            [install]
            prefix={script.scratch_path}""".format(**locals())))

    result = script.pip(
        'install', '--user', '--no-index',
        '-f', data.find_links,
        'requiresupper')
    # Check that we are really installing a wheel
    assert 'Running setup.py install for requiresupper' not in result.stdout
    assert 'installed requiresupper' in result.stdout


def test_install_option_in_requirements_file(script, data, virtualenv):
    """
    Test --install-option in requirements file overrides same option in cli
    """

    script.scratch_path.joinpath("home1").mkdir()
    script.scratch_path.joinpath("home2").mkdir()

    script.scratch_path.joinpath("reqs.txt").write_text(
        textwrap.dedent(
            """simple --install-option='--home={home}'""".format(
                home=script.scratch_path.joinpath("home1"))))

    result = script.pip(
        'install', '--no-index', '-f', data.find_links, '-r',
        script.scratch_path / 'reqs.txt',
        '--install-option=--home={home}'.format(
            home=script.scratch_path.joinpath("home2")),
        expect_stderr=True)

    package_dir = script.scratch / 'home1' / 'lib' / 'python' / 'simple'
    assert package_dir in result.files_created


def test_constraints_not_installed_by_default(script, data):
    script.scratch_path.joinpath("c.txt").write_text("requiresupper")
    result = script.pip(
        'install', '--no-index', '-f', data.find_links, '-c',
        script.scratch_path / 'c.txt', 'Upper')
    assert 'requiresupper' not in result.stdout


def test_constraints_only_causes_error(script, data):
    script.scratch_path.joinpath("c.txt").write_text("requiresupper")
    result = script.pip(
        'install', '--no-index', '-f', data.find_links, '-c',
        script.scratch_path / 'c.txt', expect_error=True)
    assert 'installed requiresupper' not in result.stdout


def test_constraints_local_editable_install_causes_error(script, data):
    script.scratch_path.joinpath("constraints.txt").write_text(
        "singlemodule==0.0.0"
    )
    to_install = data.src.joinpath("singlemodule")
    result = script.pip(
        'install', '--no-index', '-f', data.find_links, '-c',
        script.scratch_path / 'constraints.txt', '-e',
        to_install, expect_error=True)
    assert 'Could not satisfy constraints for' in result.stderr


@pytest.mark.network
def test_constraints_local_editable_install_pep518(script, data):
    to_install = data.src.joinpath("pep518-3.0")

    script.pip('download', 'setuptools', 'wheel', '-d', data.packages)
    script.pip(
        'install', '--no-index', '-f', data.find_links, '-e', to_install)


def test_constraints_local_install_causes_error(script, data):
    script.scratch_path.joinpath("constraints.txt").write_text(
        "singlemodule==0.0.0"
    )
    to_install = data.src.joinpath("singlemodule")
    result = script.pip(
        'install', '--no-index', '-f', data.find_links, '-c',
        script.scratch_path / 'constraints.txt',
        to_install, expect_error=True)
    assert 'Could not satisfy constraints for' in result.stderr


def test_constraints_constrain_to_local_editable(script, data):
    to_install = data.src.joinpath("singlemodule")
    script.scratch_path.joinpath("constraints.txt").write_text(
        "-e {url}#egg=singlemodule".format(url=path_to_url(to_install))
    )
    result = script.pip(
        'install', '--no-index', '-f', data.find_links, '-c',
        script.scratch_path / 'constraints.txt', 'singlemodule')
    assert 'Running setup.py develop for singlemodule' in result.stdout


def test_constraints_constrain_to_local(script, data):
    to_install = data.src.joinpath("singlemodule")
    script.scratch_path.joinpath("constraints.txt").write_text(
        "{url}#egg=singlemodule".format(url=path_to_url(to_install))
    )
    result = script.pip(
        'install', '--no-index', '-f', data.find_links, '-c',
        script.scratch_path / 'constraints.txt', 'singlemodule')
    assert 'Running setup.py install for singlemodule' in result.stdout


def test_constrained_to_url_install_same_url(script, data):
    to_install = data.src.joinpath("singlemodule")
    constraints = path_to_url(to_install) + "#egg=singlemodule"
    script.scratch_path.joinpath("constraints.txt").write_text(constraints)
    result = script.pip(
        'install', '--no-index', '-f', data.find_links, '-c',
        script.scratch_path / 'constraints.txt', to_install)
    assert ('Running setup.py install for singlemodule'
            in result.stdout), str(result)


def test_double_install_spurious_hash_mismatch(
        script, tmpdir, data, with_wheel):
    """Make sure installing the same hashed sdist twice doesn't throw hash
    mismatch errors.

    Really, this is a test that we disable reads from the wheel cache in
    hash-checking mode. Locally, implicitly built wheels of sdists obviously
    have different hashes from the original archives. Comparing against those
    causes spurious mismatch errors.

    """
    # Install wheel package, otherwise, it won't try to build wheels.
    with requirements_file('simple==1.0 --hash=sha256:393043e672415891885c9a2a'
                           '0929b1af95fb866d6ca016b42d2e6ce53619b653',
                           tmpdir) as reqs_file:
        # Install a package (and build its wheel):
        result = script.pip_install_local(
            '--find-links', data.find_links,
            '-r', reqs_file.resolve(),
        )
        assert 'Successfully installed simple-1.0' in str(result)

        # Uninstall it:
        script.pip('uninstall', '-y', 'simple')

        # Then install it again. We should not hit a hash mismatch, and the
        # package should install happily.
        result = script.pip_install_local(
            '--find-links', data.find_links,
            '-r', reqs_file.resolve(),
        )
        assert 'Successfully installed simple-1.0' in str(result)


def test_install_with_extras_from_constraints(script, data):
    to_install = data.packages.joinpath("LocalExtras")
    script.scratch_path.joinpath("constraints.txt").write_text(
        "{url}#egg=LocalExtras[bar]".format(url=path_to_url(to_install))
    )
    result = script.pip_install_local(
        '-c', script.scratch_path / 'constraints.txt', 'LocalExtras')
    assert script.site_packages / 'simple' in result.files_created


def test_install_with_extras_from_install(script, data):
    to_install = data.packages.joinpath("LocalExtras")
    script.scratch_path.joinpath("constraints.txt").write_text(
        "{url}#egg=LocalExtras".format(url=path_to_url(to_install))
    )
    result = script.pip_install_local(
        '-c', script.scratch_path / 'constraints.txt', 'LocalExtras[baz]')
    assert script.site_packages / 'singlemodule.py'in result.files_created


def test_install_with_extras_joined(script, data):
    to_install = data.packages.joinpath("LocalExtras")
    script.scratch_path.joinpath("constraints.txt").write_text(
        "{url}#egg=LocalExtras[bar]".format(url=path_to_url(to_install))
    )
    result = script.pip_install_local(
        '-c', script.scratch_path / 'constraints.txt', 'LocalExtras[baz]'
    )
    assert script.site_packages / 'simple' in result.files_created
    assert script.site_packages / 'singlemodule.py'in result.files_created


def test_install_with_extras_editable_joined(script, data):
    to_install = data.packages.joinpath("LocalExtras")
    script.scratch_path.joinpath("constraints.txt").write_text(
        "-e {url}#egg=LocalExtras[bar]".format(url=path_to_url(to_install))
    )
    result = script.pip_install_local(
        '-c', script.scratch_path / 'constraints.txt', 'LocalExtras[baz]')
    assert script.site_packages / 'simple' in result.files_created
    assert script.site_packages / 'singlemodule.py'in result.files_created


def test_install_distribution_full_union(script, data):
    to_install = data.packages.joinpath("LocalExtras")
    result = script.pip_install_local(
        to_install, to_install + "[bar]", to_install + "[baz]")
    assert 'Running setup.py install for LocalExtras' in result.stdout
    assert script.site_packages / 'simple' in result.files_created
    assert script.site_packages / 'singlemodule.py' in result.files_created


def test_install_distribution_duplicate_extras(script, data):
    to_install = data.packages.joinpath("LocalExtras")
    package_name = to_install + "[bar]"
    with pytest.raises(AssertionError):
        result = script.pip_install_local(package_name, package_name)
        expected = (
            'Double requirement given: {package_name}'.format(**locals()))
        assert expected in result.stderr


def test_install_distribution_union_with_constraints(script, data):
    to_install = data.packages.joinpath("LocalExtras")
    script.scratch_path.joinpath("constraints.txt").write_text(
        "{to_install}[bar]".format(**locals()))
    result = script.pip_install_local(
        '-c', script.scratch_path / 'constraints.txt', to_install + '[baz]')
    assert 'Running setup.py install for LocalExtras' in result.stdout
    assert script.site_packages / 'singlemodule.py' in result.files_created


def test_install_distribution_union_with_versions(script, data):
    to_install_001 = data.packages.joinpath("LocalExtras")
    to_install_002 = data.packages.joinpath("LocalExtras-0.0.2")
    result = script.pip_install_local(
        to_install_001 + "[bar]", to_install_002 + "[baz]")
    assert ("Successfully installed LocalExtras-0.0.1 simple-3.0 " +
            "singlemodule-0.0.1" in result.stdout)


@pytest.mark.xfail
def test_install_distribution_union_conflicting_extras(script, data):
    # LocalExtras requires simple==1.0, LocalExtras[bar] requires simple==2.0;
    # without a resolver, pip does not detect the conflict between simple==1.0
    # and simple==2.0. Once a resolver is added, this conflict should be
    # detected.
    to_install = data.packages.joinpath("LocalExtras-0.0.2")
    result = script.pip_install_local(to_install, to_install + "[bar]",
                                      expect_error=True)
    assert 'installed' not in result.stdout
    assert "Conflict" in result.stderr


def test_install_unsupported_wheel_link_with_marker(script):
    script.scratch_path.joinpath("with-marker.txt").write_text(
        textwrap.dedent("""\
            {url}; {req}
        """).format(
            url='https://github.com/a/b/c/asdf-1.5.2-cp27-none-xyz.whl',
            req='sys_platform == "xyz"',
        )
    )
    result = script.pip(
        'install', '-r', script.scratch_path / 'with-marker.txt'
    )

    assert ("Ignoring asdf: markers 'sys_platform == \"xyz\"' don't match "
            "your environment") in result.stdout
    assert len(result.files_created) == 0


def test_install_unsupported_wheel_file(script, data):
    # Trying to install a local wheel with an incompatible version/type
    # should fail.
    path = data.packages.joinpath("simple.dist-0.1-py1-none-invalid.whl")
    script.scratch_path.joinpath("wheel-file.txt").write_text(textwrap.dedent("""\
        {path}
        """.format(**locals())))
    result = script.pip(
        'install', '-r', script.scratch_path / 'wheel-file.txt',
        expect_error=True,
        expect_stderr=True,
    )
    assert ("simple.dist-0.1-py1-none-invalid.whl is not a supported " +
            "wheel on this platform" in result.stderr)
    assert len(result.files_created) == 0


def test_install_options_local_to_package(script, data):
    """Make sure --install-options does not leak across packages.

    A requirements.txt file can have per-package --install-options; these
    should be isolated to just the package instead of leaking to subsequent
    packages.  This needs to be a functional test because the bug was around
    cross-contamination at install time.
    """
    home_simple = script.scratch_path.joinpath("for-simple")
    test_simple = script.scratch.joinpath("for-simple")
    home_simple.mkdir()
    reqs_file = script.scratch_path.joinpath("reqs.txt")
    reqs_file.write_text(
        textwrap.dedent("""
            simple --install-option='--home={home_simple}'
            INITools
            """.format(**locals())))
    result = script.pip(
        'install',
        '--no-index', '-f', data.find_links,
        '-r', reqs_file,
        expect_stderr=True,
    )

    simple = test_simple / 'lib' / 'python' / 'simple'
    bad = test_simple / 'lib' / 'python' / 'initools'
    good = script.site_packages / 'initools'
    assert simple in result.files_created
    assert result.files_created[simple].dir
    assert bad not in result.files_created
    assert good in result.files_created
    assert result.files_created[good].dir
