from typing import Collection

from tests.lib import PipTestEnvironment, create_test_package_with_setup


def matches_expected_lines(string: str, expected_lines: Collection[str]) -> bool:
    # Ignore empty lines
    output_lines = list(filter(None, string.splitlines()))
    # We'll match the last n lines, given n lines to match.
    last_few_output_lines = output_lines[-len(expected_lines) :]
    # And order does not matter
    return set(last_few_output_lines) == set(expected_lines)


def test_basic_check_clean(script: PipTestEnvironment) -> None:
    """On a clean environment, check should print a helpful message."""
    result = script.pip("check")

    expected_lines = ("No broken requirements found.",)
    assert matches_expected_lines(result.stdout, expected_lines)
    assert result.returncode == 0


def test_basic_check_missing_dependency(script: PipTestEnvironment) -> None:
    # Setup a small project
    pkga_path = create_test_package_with_setup(
        script,
        name="pkga",
        version="1.0",
        install_requires=["missing==0.1"],
    )
    # Let's install pkga without its dependency
    res = script.pip("install", "--no-index", pkga_path, "--no-deps")
    assert "Successfully installed pkga-1.0" in res.stdout, str(res)

    result = script.pip("check", expect_error=True)

    expected_lines = ("pkga 1.0 requires missing, which is not installed.",)
    assert matches_expected_lines(result.stdout, expected_lines)
    assert result.returncode == 1


def test_basic_check_broken_dependency(script: PipTestEnvironment) -> None:
    # Setup pkga depending on pkgb>=1.0
    pkga_path = create_test_package_with_setup(
        script,
        name="pkga",
        version="1.0",
        install_requires=["broken>=1.0"],
    )
    # Let's install pkga without its dependency
    res = script.pip("install", "--no-index", pkga_path, "--no-deps")
    assert "Successfully installed pkga-1.0" in res.stdout, str(res)

    # Setup broken==0.1
    broken_path = create_test_package_with_setup(
        script,
        name="broken",
        version="0.1",
    )
    # Let's install broken==0.1
    res = script.pip(
        "install",
        "--no-index",
        broken_path,
        "--no-warn-conflicts",
    )
    assert "Successfully installed broken-0.1" in res.stdout, str(res)

    result = script.pip("check", expect_error=True)

    expected_lines = ("pkga 1.0 has requirement broken>=1.0, but you have broken 0.1.",)
    assert matches_expected_lines(result.stdout, expected_lines)
    assert result.returncode == 1


def test_basic_check_broken_dependency_and_missing_dependency(
    script: PipTestEnvironment,
) -> None:
    pkga_path = create_test_package_with_setup(
        script,
        name="pkga",
        version="1.0",
        install_requires=["broken>=1.0"],
    )
    # Let's install pkga without its dependency
    res = script.pip("install", "--no-index", pkga_path, "--no-deps")
    assert "Successfully installed pkga-1.0" in res.stdout, str(res)

    # Setup broken==0.1
    broken_path = create_test_package_with_setup(
        script,
        name="broken",
        version="0.1",
        install_requires=["missing"],
    )
    # Let's install broken==0.1
    res = script.pip("install", "--no-index", broken_path, "--no-deps")
    assert "Successfully installed broken-0.1" in res.stdout, str(res)

    result = script.pip("check", expect_error=True)

    expected_lines = (
        "broken 0.1 requires missing, which is not installed.",
        "pkga 1.0 has requirement broken>=1.0, but you have broken 0.1.",
    )

    assert matches_expected_lines(result.stdout, expected_lines)
    assert result.returncode == 1


def test_check_complicated_name_missing(script: PipTestEnvironment) -> None:
    package_a_path = create_test_package_with_setup(
        script,
        name="package_A",
        version="1.0",
        install_requires=["Dependency-B>=1.0"],
    )

    # Without dependency
    result = script.pip("install", "--no-index", package_a_path, "--no-deps")
    assert (
        "Successfully installed package_A-1.0" in result.stdout
        or "Successfully installed package-A-1.0" in result.stdout
    ), str(result)

    result = script.pip("check", expect_error=True)
    expected_lines = ("package-a 1.0 requires dependency-b, which is not installed.",)
    assert matches_expected_lines(result.stdout, expected_lines)
    assert result.returncode == 1


def test_check_complicated_name_broken(script: PipTestEnvironment) -> None:
    package_a_path = create_test_package_with_setup(
        script,
        name="package_A",
        version="1.0",
        install_requires=["Dependency-B>=1.0"],
    )
    dependency_b_path_incompatible = create_test_package_with_setup(
        script,
        name="dependency-b",
        version="0.1",
    )

    # With broken dependency
    result = script.pip("install", "--no-index", package_a_path, "--no-deps")
    assert (
        "Successfully installed package_A-1.0" in result.stdout
        or "Successfully installed package-A-1.0" in result.stdout
    ), str(result)

    result = script.pip(
        "install",
        "--no-index",
        dependency_b_path_incompatible,
        "--no-deps",
    )
    assert "Successfully installed dependency-b-0.1" in result.stdout

    result = script.pip("check", expect_error=True)
    expected_lines = (
        "package-a 1.0 has requirement Dependency-B>=1.0, but you have "
        "dependency-b 0.1.",
    )
    assert matches_expected_lines(result.stdout, expected_lines)
    assert result.returncode == 1


def test_check_complicated_name_clean(script: PipTestEnvironment) -> None:
    package_a_path = create_test_package_with_setup(
        script,
        name="package_A",
        version="1.0",
        install_requires=["Dependency-B>=1.0"],
    )
    dependency_b_path = create_test_package_with_setup(
        script,
        name="dependency-b",
        version="1.0",
    )

    result = script.pip("install", "--no-index", package_a_path, "--no-deps")
    assert (
        "Successfully installed package_A-1.0" in result.stdout
        or "Successfully installed package-A-1.0" in result.stdout
    ), str(result)

    result = script.pip(
        "install",
        "--no-index",
        dependency_b_path,
        "--no-deps",
    )
    assert "Successfully installed dependency-b-1.0" in result.stdout

    result = script.pip("check")
    expected_lines = ("No broken requirements found.",)
    assert matches_expected_lines(result.stdout, expected_lines)
    assert result.returncode == 0


def test_check_considers_conditional_reqs(script: PipTestEnvironment) -> None:
    package_a_path = create_test_package_with_setup(
        script,
        name="package_A",
        version="1.0",
        install_requires=[
            "Dependency-B>=1.0; python_version != '2.7'",
            "Dependency-B>=2.0; python_version == '2.7'",
        ],
    )

    result = script.pip("install", "--no-index", package_a_path, "--no-deps")
    assert (
        "Successfully installed package_A-1.0" in result.stdout
        or "Successfully installed package-A-1.0" in result.stdout
    ), str(result)

    result = script.pip("check", expect_error=True)
    expected_lines = ("package-a 1.0 requires dependency-b, which is not installed.",)
    assert matches_expected_lines(result.stdout, expected_lines)
    assert result.returncode == 1


def test_check_development_versions_are_also_considered(
    script: PipTestEnvironment,
) -> None:
    # Setup pkga depending on pkgb>=1.0
    pkga_path = create_test_package_with_setup(
        script,
        name="pkga",
        version="1.0",
        install_requires=["depend>=1.0"],
    )
    # Let's install pkga without its dependency
    res = script.pip("install", "--no-index", pkga_path, "--no-deps")
    assert "Successfully installed pkga-1.0" in res.stdout, str(res)

    # Setup depend==1.1.0.dev0
    depend_path = create_test_package_with_setup(
        script,
        name="depend",
        version="1.1.0.dev0",
    )
    # Let's install depend==1.1.0.dev0
    res = script.pip(
        "install",
        "--no-index",
        depend_path,
        "--no-warn-conflicts",
    )
    assert "Successfully installed depend-1.1.0.dev0" in res.stdout, str(res)

    result = script.pip("check")
    expected_lines = ("No broken requirements found.",)
    assert matches_expected_lines(result.stdout, expected_lines)
    assert result.returncode == 0


def test_basic_check_broken_metadata(script: PipTestEnvironment) -> None:
    # Create some corrupt metadata
    dist_info_dir = script.site_packages_path / "pkga-1.0.dist-info"
    dist_info_dir.mkdir()
    with open(dist_info_dir / "METADATA", "w") as f:
        f.write(
            "Metadata-Version: 2.1\n"
            "Name: pkga\n"
            "Version: 1.0\n"
            'Requires-Dist: pip; python_version == "3.4";extra == "test"\n'
        )

    result = script.pip("check", expect_error=True)

    assert "Error parsing dependencies of" in result.stderr
    assert result.returncode == 1


def test_check_skip_work_dir_pkg(script: PipTestEnvironment) -> None:
    """
    Test that check should not include package
    present in working directory
    """

    # Create a test package with dependency missing
    # and create .egg-info dir
    pkg_path = create_test_package_with_setup(
        script, name="simple", version="1.0", install_requires=["missing==0.1"]
    )

    script.run("python", "setup.py", "egg_info", expect_stderr=True, cwd=pkg_path)

    # Check should not complain about broken requirements
    # when run from package directory
    result = script.pip("check", cwd=pkg_path)
    expected_lines = ("No broken requirements found.",)
    assert matches_expected_lines(result.stdout, expected_lines)
    assert result.returncode == 0


def test_check_include_work_dir_pkg(script: PipTestEnvironment) -> None:
    """
    Test that check should include package in working directory
    if working directory is added in PYTHONPATH
    """

    # Create a test package with dependency missing
    # and create .egg-info dir
    pkg_path = create_test_package_with_setup(
        script, name="simple", version="1.0", install_requires=["missing==0.1"]
    )

    script.run("python", "setup.py", "egg_info", expect_stderr=True, cwd=pkg_path)

    script.environ.update({"PYTHONPATH": pkg_path})

    # Check should mention about missing requirement simple
    # when run from package directory, when package directory
    # is in PYTHONPATH
    result = script.pip("check", expect_error=True, cwd=pkg_path)
    expected_lines = ("simple 1.0 requires missing, which is not installed.",)
    assert matches_expected_lines(result.stdout, expected_lines)
    assert result.returncode == 1
