import pytest

from clikit.io import NullIO

from poetry.core.packages import ProjectPackage
from poetry.core.packages import dependency_from_pep_508
from poetry.core.version.markers import parse_marker
from poetry.puzzle import Solver
from poetry.puzzle.exceptions import SolverProblemError
from poetry.puzzle.provider import Provider as BaseProvider
from poetry.repositories.installed_repository import InstalledRepository
from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository
from poetry.utils._compat import Path
from poetry.utils.env import MockEnv
from tests.helpers import get_dependency
from tests.helpers import get_package
from tests.repositories.test_legacy_repository import (
    MockRepository as MockLegacyRepository,
)
from tests.repositories.test_pypi_repository import MockRepository as MockPyPIRepository


class Provider(BaseProvider):
    def set_package_python_versions(self, python_versions):
        self._package.python_versions = python_versions
        self._python_constraint = self._package.python_constraint


@pytest.fixture()
def io():
    return NullIO()


@pytest.fixture()
def package():
    return ProjectPackage("root", "1.0")


@pytest.fixture()
def installed():
    return InstalledRepository()


@pytest.fixture()
def locked():
    return Repository()


@pytest.fixture()
def repo():
    return Repository()


@pytest.fixture()
def pool(repo):
    return Pool([repo])


@pytest.fixture()
def solver(package, pool, installed, locked, io):
    return Solver(
        package, pool, installed, locked, io, provider=Provider(package, pool, io)
    )


def check_solver_result(ops, expected):
    for e in expected:
        if "skipped" not in e:
            e["skipped"] = False

    result = []
    for op in ops:
        if "update" == op.job_type:
            result.append(
                {
                    "job": "update",
                    "from": op.initial_package,
                    "to": op.target_package,
                    "skipped": op.skipped,
                }
            )
        else:
            job = "install"
            if op.job_type == "uninstall":
                job = "remove"

            result.append({"job": job, "package": op.package, "skipped": op.skipped})

    assert result == expected


def test_solver_install_single(solver, repo, package):
    package.add_dependency("A")

    package_a = get_package("A", "1.0")
    repo.add_package(package_a)

    ops = solver.solve([get_dependency("A")])

    check_solver_result(ops, [{"job": "install", "package": package_a}])


def test_solver_remove_if_no_longer_locked(solver, locked, installed):
    package_a = get_package("A", "1.0")
    installed.add_package(package_a)
    locked.add_package(package_a)

    ops = solver.solve()

    check_solver_result(ops, [{"job": "remove", "package": package_a}])


def test_remove_non_installed(solver, repo, locked):
    package_a = get_package("A", "1.0")
    locked.add_package(package_a)

    repo.add_package(package_a)

    request = []

    ops = solver.solve(request)

    check_solver_result(ops, [{"job": "remove", "package": package_a, "skipped": True}])


def test_install_non_existing_package_fail(solver, repo, package):
    package.add_dependency("B", "1")

    package_a = get_package("A", "1.0")
    repo.add_package(package_a)

    with pytest.raises(SolverProblemError):
        solver.solve()


def test_solver_with_deps(solver, repo, package):
    package.add_dependency("A")

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    new_package_b = get_package("B", "1.1")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(new_package_b)

    package_a.requires.append(get_dependency("B", "<1.1"))

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_b},
            {"job": "install", "package": package_a},
        ],
    )


def test_install_honours_not_equal(solver, repo, package):
    package.add_dependency("A")

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    new_package_b11 = get_package("B", "1.1")
    new_package_b12 = get_package("B", "1.2")
    new_package_b13 = get_package("B", "1.3")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(new_package_b11)
    repo.add_package(new_package_b12)
    repo.add_package(new_package_b13)

    package_a.requires.append(get_dependency("B", "<=1.3,!=1.3,!=1.2"))

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": new_package_b11},
            {"job": "install", "package": package_a},
        ],
    )


def test_install_with_deps_in_order(solver, repo, package):
    package.add_dependency("A")
    package.add_dependency("B")
    package.add_dependency("C")

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_c = get_package("C", "1.0")
    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)

    package_b.requires.append(get_dependency("A", ">=1.0"))
    package_b.requires.append(get_dependency("C", ">=1.0"))

    package_c.requires.append(get_dependency("A", ">=1.0"))

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_b},
        ],
    )


def test_install_installed(solver, repo, installed, package):
    package.add_dependency("A")

    package_a = get_package("A", "1.0")
    installed.add_package(package_a)
    repo.add_package(package_a)

    ops = solver.solve()

    check_solver_result(
        ops, [{"job": "install", "package": package_a, "skipped": True}]
    )


def test_update_installed(solver, repo, installed, package):
    package.add_dependency("A")

    installed.add_package(get_package("A", "1.0"))

    package_a = get_package("A", "1.0")
    new_package_a = get_package("A", "1.1")
    repo.add_package(package_a)
    repo.add_package(new_package_a)

    ops = solver.solve()

    check_solver_result(
        ops, [{"job": "update", "from": package_a, "to": new_package_a}]
    )


def test_update_with_use_latest(solver, repo, installed, package, locked):
    package.add_dependency("A")
    package.add_dependency("B")

    installed.add_package(get_package("A", "1.0"))

    package_a = get_package("A", "1.0")
    new_package_a = get_package("A", "1.1")
    package_b = get_package("B", "1.0")
    new_package_b = get_package("B", "1.1")
    repo.add_package(package_a)
    repo.add_package(new_package_a)
    repo.add_package(package_b)
    repo.add_package(new_package_b)

    locked.add_package(package_a)
    locked.add_package(package_b)

    ops = solver.solve(use_latest=[package_b.name])

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_a, "skipped": True},
            {"job": "install", "package": new_package_b},
        ],
    )


def test_solver_sets_categories(solver, repo, package):
    package.add_dependency("A")
    package.add_dependency("B", category="dev")

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_c = get_package("C", "1.0")
    package_b.add_dependency("C", "~1.0")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_b},
        ],
    )

    assert ops[0].package.category == "dev"
    assert ops[2].package.category == "dev"
    assert ops[1].package.category == "main"


def test_solver_respects_root_package_python_versions(solver, repo, package):
    solver.provider.set_package_python_versions("~3.4")
    package.add_dependency("A")
    package.add_dependency("B")

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_b.python_versions = "^3.3"
    package_c = get_package("C", "1.0")
    package_c.python_versions = "^3.4"
    package_c11 = get_package("C", "1.1")
    package_c11.python_versions = "^3.6"
    package_b.add_dependency("C", "^1.0")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)
    repo.add_package(package_c11)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_b},
        ],
    )


def test_solver_fails_if_mismatch_root_python_versions(solver, repo, package):
    solver.provider.set_package_python_versions("^3.4")
    package.add_dependency("A")
    package.add_dependency("B")

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_b.python_versions = "^3.6"
    package_c = get_package("C", "1.0")
    package_c.python_versions = "~3.3"
    package_b.add_dependency("C", "~1.0")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)

    with pytest.raises(SolverProblemError):
        solver.solve()


def test_solver_solves_optional_and_compatible_packages(solver, repo, package):
    solver.provider.set_package_python_versions("~3.4")
    package.extras["foo"] = [get_dependency("B")]
    package.add_dependency("A", {"version": "*", "python": "^3.4"})
    package.add_dependency("B", {"version": "*", "optional": True})

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_b.python_versions = "^3.3"
    package_c = get_package("C", "1.0")
    package_c.python_versions = "^3.4"
    package_b.add_dependency("C", "^1.0")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_b},
        ],
    )


def test_solver_does_not_return_extras_if_not_requested(solver, repo, package):
    package.add_dependency("A")
    package.add_dependency("B")

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_c = get_package("C", "1.0")

    package_b.extras = {"foo": [get_dependency("C", "^1.0")]}

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_b},
        ],
    )


def test_solver_returns_extras_if_requested(solver, repo, package):
    package.add_dependency("A")
    package.add_dependency("B", {"version": "*", "extras": ["foo"]})

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_c = get_package("C", "1.0")

    dep = get_dependency("C", "^1.0", optional=True)
    dep.marker = parse_marker("extra == 'foo'")
    package_b.extras = {"foo": [dep]}
    package_b.requires.append(dep)

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_b},
        ],
    )

    assert ops[-1].package.marker.is_any()
    assert ops[0].package.marker.is_any()


def test_solver_returns_prereleases_if_requested(solver, repo, package):
    package.add_dependency("A")
    package.add_dependency("B")
    package.add_dependency("C", {"version": "*", "allow-prereleases": True})

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_c = get_package("C", "1.0")
    package_c_dev = get_package("C", "1.1-beta.1")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)
    repo.add_package(package_c_dev)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_b},
            {"job": "install", "package": package_c_dev},
        ],
    )


def test_solver_does_not_return_prereleases_if_not_requested(solver, repo, package):
    package.add_dependency("A")
    package.add_dependency("B")
    package.add_dependency("C")

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_c = get_package("C", "1.0")
    package_c_dev = get_package("C", "1.1-beta.1")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)
    repo.add_package(package_c_dev)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_b},
            {"job": "install", "package": package_c},
        ],
    )


def test_solver_sub_dependencies_with_requirements(solver, repo, package):
    package.add_dependency("A")
    package.add_dependency("B")

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_c = get_package("C", "1.0")
    package_d = get_package("D", "1.0")

    package_c.add_dependency("D", {"version": "^1.0", "python": "<4.0"})
    package_a.add_dependency("C")
    package_b.add_dependency("D", "^1.0")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)
    repo.add_package(package_d)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_d},
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_b},
        ],
    )

    op = ops[1]
    assert op.package.marker.is_any()


def test_solver_sub_dependencies_with_requirements_complex(solver, repo, package):
    package.add_dependency("A", {"version": "^1.0", "python": "<5.0"})
    package.add_dependency("B", {"version": "^1.0", "python": "<5.0"})
    package.add_dependency("C", {"version": "^1.0", "python": "<4.0"})

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_c = get_package("C", "1.0")
    package_d = get_package("D", "1.0")
    package_e = get_package("E", "1.0")
    package_f = get_package("F", "1.0")

    package_a.add_dependency("B", {"version": "^1.0", "python": "<4.0"})
    package_a.add_dependency("D", {"version": "^1.0", "python": "<4.0"})
    package_b.add_dependency("E", {"version": "^1.0", "platform": "win32"})
    package_b.add_dependency("F", {"version": "^1.0", "python": "<5.0"})
    package_c.add_dependency("F", {"version": "^1.0", "python": "<4.0"})
    package_d.add_dependency("F")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)
    repo.add_package(package_d)
    repo.add_package(package_e)
    repo.add_package(package_f)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_e},
            {"job": "install", "package": package_f},
            {"job": "install", "package": package_b},
            {"job": "install", "package": package_d},
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_c},
        ],
    )


def test_solver_sub_dependencies_with_not_supported_python_version(
    solver, repo, package
):
    solver.provider.set_package_python_versions("^3.5")
    package.add_dependency("A")

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_b.python_versions = "<2.0"

    package_a.add_dependency("B", {"version": "^1.0", "python": "<2.0"})

    repo.add_package(package_a)
    repo.add_package(package_b)

    ops = solver.solve()

    check_solver_result(ops, [{"job": "install", "package": package_a}])


def test_solver_with_dependency_in_both_main_and_dev_dependencies(
    solver, repo, package
):
    solver.provider.set_package_python_versions("^3.5")
    package.add_dependency("A")
    package.add_dependency("A", {"version": "*", "extras": ["foo"]}, category="dev")

    package_a = get_package("A", "1.0")
    package_a.extras["foo"] = [get_dependency("C")]
    package_a.add_dependency("C", {"version": "^1.0", "optional": True})
    package_a.add_dependency("B", {"version": "^1.0"})

    package_b = get_package("B", "1.0")

    package_c = get_package("C", "1.0")
    package_c.add_dependency("D", "^1.0")

    package_d = get_package("D", "1.0")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)
    repo.add_package(package_d)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_d},
            {"job": "install", "package": package_b},
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_a},
        ],
    )

    b = ops[1].package
    c = ops[2].package
    d = ops[0].package
    a = ops[3].package

    assert d.category == "dev"
    assert c.category == "dev"
    assert b.category == "main"
    assert a.category == "main"


def test_solver_with_dependency_in_both_main_and_dev_dependencies_with_one_more_dependent(
    solver, repo, package
):
    package.add_dependency("A")
    package.add_dependency("E")
    package.add_dependency("A", {"version": "*", "extras": ["foo"]}, category="dev")

    package_a = get_package("A", "1.0")
    package_a.extras["foo"] = [get_dependency("C")]
    package_a.add_dependency("C", {"version": "^1.0", "optional": True})
    package_a.add_dependency("B", {"version": "^1.0"})

    package_b = get_package("B", "1.0")

    package_c = get_package("C", "1.0")
    package_c.add_dependency("D", "^1.0")

    package_d = get_package("D", "1.0")

    package_e = get_package("E", "1.0")
    package_e.add_dependency("A", "^1.0")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)
    repo.add_package(package_d)
    repo.add_package(package_e)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_b},
            {"job": "install", "package": package_d},
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_e},
        ],
    )

    b = ops[0].package
    c = ops[3].package
    d = ops[1].package
    a = ops[2].package
    e = ops[4].package

    assert d.category == "dev"
    assert c.category == "dev"
    assert b.category == "main"
    assert a.category == "main"
    assert e.category == "main"


def test_solver_with_dependency_and_prerelease_sub_dependencies(solver, repo, package):
    package.add_dependency("A")

    package_a = get_package("A", "1.0")
    package_a.add_dependency("B", ">=1.0.0.dev2")

    repo.add_package(package_a)
    repo.add_package(get_package("B", "0.9.0"))
    repo.add_package(get_package("B", "1.0.0.dev1"))
    repo.add_package(get_package("B", "1.0.0.dev2"))
    repo.add_package(get_package("B", "1.0.0.dev3"))
    package_b = get_package("B", "1.0.0.dev4")
    repo.add_package(package_b)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_b},
            {"job": "install", "package": package_a},
        ],
    )


def test_solver_circular_dependency(solver, repo, package):
    package.add_dependency("A")

    package_a = get_package("A", "1.0")
    package_a.add_dependency("B", "^1.0")

    package_b = get_package("B", "1.0")
    package_b.add_dependency("A", "^1.0")
    package_b.add_dependency("C", "^1.0")

    package_c = get_package("C", "1.0")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_b},
            {"job": "install", "package": package_a},
        ],
    )

    assert "main" == ops[0].package.category


def test_solver_duplicate_dependencies_same_constraint(solver, repo, package):
    package.add_dependency("A")

    package_a = get_package("A", "1.0")
    package_a.add_dependency("B", {"version": "^1.0", "python": "2.7"})
    package_a.add_dependency("B", {"version": "^1.0", "python": ">=3.4"})

    package_b = get_package("B", "1.0")

    repo.add_package(package_a)
    repo.add_package(package_b)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_b},
            {"job": "install", "package": package_a},
        ],
    )


def test_solver_duplicate_dependencies_different_constraints(solver, repo, package):
    package.add_dependency("A")

    package_a = get_package("A", "1.0")
    package_a.add_dependency("B", {"version": "^1.0", "python": "<3.4"})
    package_a.add_dependency("B", {"version": "^2.0", "python": ">=3.4"})

    package_b10 = get_package("B", "1.0")
    package_b20 = get_package("B", "2.0")

    repo.add_package(package_a)
    repo.add_package(package_b10)
    repo.add_package(package_b20)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_b10},
            {"job": "install", "package": package_b20},
            {"job": "install", "package": package_a},
        ],
    )


def test_solver_duplicate_dependencies_different_constraints_same_requirements(
    solver, repo, package
):
    package.add_dependency("A")

    package_a = get_package("A", "1.0")
    package_a.add_dependency("B", {"version": "^1.0"})
    package_a.add_dependency("B", {"version": "^2.0"})

    package_b10 = get_package("B", "1.0")
    package_b20 = get_package("B", "2.0")

    repo.add_package(package_a)
    repo.add_package(package_b10)
    repo.add_package(package_b20)

    with pytest.raises(SolverProblemError) as e:
        solver.solve()

    expected = """\
Because a (1.0) depends on both B (^1.0) and B (^2.0), a is forbidden.
So, because no versions of a match !=1.0
 and root depends on A (*), version solving failed."""

    assert str(e.value) == expected


def test_solver_duplicate_dependencies_sub_dependencies(solver, repo, package):
    package.add_dependency("A")

    package_a = get_package("A", "1.0")
    package_a.add_dependency("B", {"version": "^1.0", "python": "<3.4"})
    package_a.add_dependency("B", {"version": "^2.0", "python": ">=3.4"})

    package_b10 = get_package("B", "1.0")
    package_b20 = get_package("B", "2.0")
    package_b10.add_dependency("C", "1.2")
    package_b20.add_dependency("C", "1.5")

    package_c12 = get_package("C", "1.2")
    package_c15 = get_package("C", "1.5")

    repo.add_package(package_a)
    repo.add_package(package_b10)
    repo.add_package(package_b20)
    repo.add_package(package_c12)
    repo.add_package(package_c15)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_c12},
            {"job": "install", "package": package_c15},
            {"job": "install", "package": package_b10},
            {"job": "install", "package": package_b20},
            {"job": "install", "package": package_a},
        ],
    )


def test_solver_fails_if_dependency_name_does_not_match_package(solver, repo, package):
    package.add_dependency("my-demo", {"git": "https://github.com/demo/demo.git"})

    with pytest.raises(RuntimeError):
        solver.solve()


def test_solver_does_not_get_stuck_in_recursion_on_circular_dependency(
    solver, repo, package
):
    package_a = get_package("A", "1.0")
    package_a.add_dependency("B", "^1.0")
    package_b = get_package("B", "1.0")
    package_b.add_dependency("C", "^1.0")
    package_c = get_package("C", "1.0")
    package_c.add_dependency("B", "^1.0")

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)

    package.add_dependency("A", "^1.0")

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_b},
            {"job": "install", "package": package_a},
        ],
    )


def test_solver_can_resolve_git_dependencies(solver, repo, package):
    pendulum = get_package("pendulum", "2.0.3")
    cleo = get_package("cleo", "1.0.0")
    repo.add_package(pendulum)
    repo.add_package(cleo)

    package.add_dependency("demo", {"git": "https://github.com/demo/demo.git"})

    ops = solver.solve()

    demo = get_package("demo", "0.1.2")
    demo.source_type = "git"
    demo.source_url = "https://github.com/demo/demo.git"
    demo.source_reference = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"

    check_solver_result(
        ops,
        [{"job": "install", "package": pendulum}, {"job": "install", "package": demo}],
    )

    op = ops[1]

    assert op.package.source_type == "git"
    assert op.package.source_reference.startswith("9cf87a2")


def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package):
    pendulum = get_package("pendulum", "2.0.3")
    cleo = get_package("cleo", "1.0.0")
    repo.add_package(pendulum)
    repo.add_package(cleo)

    package.add_dependency(
        "demo", {"git": "https://github.com/demo/demo.git", "extras": ["foo"]}
    )

    ops = solver.solve()

    demo = get_package("demo", "0.1.2")
    demo.source_type = "git"
    demo.source_url = "https://github.com/demo/demo.git"
    demo.source_reference = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"

    check_solver_result(
        ops,
        [
            {"job": "install", "package": cleo},
            {"job": "install", "package": pendulum},
            {"job": "install", "package": demo},
        ],
    )


@pytest.mark.parametrize(
    "ref",
    [{"branch": "a-branch"}, {"tag": "a-tag"}, {"rev": "9cf8"}],
    ids=["branch", "tag", "rev"],
)
def test_solver_can_resolve_git_dependencies_with_ref(solver, repo, package, ref):
    pendulum = get_package("pendulum", "2.0.3")
    cleo = get_package("cleo", "1.0.0")
    repo.add_package(pendulum)
    repo.add_package(cleo)

    demo = get_package("demo", "0.1.2")
    demo.source_type = "git"
    demo.source_url = "https://github.com/demo/demo.git"
    demo.source_reference = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"

    git_config = {demo.source_type: demo.source_url}
    git_config.update(ref)
    package.add_dependency("demo", git_config)

    ops = solver.solve()

    check_solver_result(
        ops,
        [{"job": "install", "package": pendulum}, {"job": "install", "package": demo}],
    )

    op = ops[1]

    assert op.package.source_type == "git"
    assert op.package.source_reference.startswith("9cf87a2")


def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible(
    solver, repo, package
):
    solver.provider.set_package_python_versions("~2.7 || ^3.4")
    package.add_dependency("A", {"version": "^1.0", "python": "^3.6"})

    package_a = get_package("A", "1.0.0")
    package_a.python_versions = ">=3.6"

    repo.add_package(package_a)

    ops = solver.solve()

    check_solver_result(ops, [{"job": "install", "package": package_a}])


def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible_multiple(
    solver, repo, package
):
    solver.provider.set_package_python_versions("~2.7 || ^3.4")
    package.add_dependency("A", {"version": "^1.0", "python": "^3.6"})
    package.add_dependency("B", {"version": "^1.0", "python": "^3.5.3"})

    package_a = get_package("A", "1.0.0")
    package_a.python_versions = ">=3.6"
    package_a.add_dependency("B", "^1.0")

    package_b = get_package("B", "1.0.0")
    package_b.python_versions = ">=3.5.3"

    repo.add_package(package_a)
    repo.add_package(package_b)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_b},
            {"job": "install", "package": package_a},
        ],
    )


def test_solver_triggers_conflict_for_dependency_python_not_fully_compatible_with_package_python(
    solver, repo, package
):
    solver.provider.set_package_python_versions("~2.7 || ^3.4")
    package.add_dependency("A", {"version": "^1.0", "python": "^3.5"})

    package_a = get_package("A", "1.0.0")
    package_a.python_versions = ">=3.6"

    repo.add_package(package_a)

    with pytest.raises(SolverProblemError):
        solver.solve()


def test_solver_finds_compatible_package_for_dependency_python_not_fully_compatible_with_package_python(
    solver, repo, package
):
    solver.provider.set_package_python_versions("~2.7 || ^3.4")
    package.add_dependency("A", {"version": "^1.0", "python": "^3.5"})

    package_a101 = get_package("A", "1.0.1")
    package_a101.python_versions = ">=3.6"

    package_a100 = get_package("A", "1.0.0")
    package_a100.python_versions = ">=3.5"

    repo.add_package(package_a100)
    repo.add_package(package_a101)

    ops = solver.solve()

    check_solver_result(ops, [{"job": "install", "package": package_a100}])


def test_solver_does_not_trigger_new_resolution_on_duplicate_dependencies_if_only_extras(
    solver, repo, package
):
    dep1 = dependency_from_pep_508('B (>=1.0); extra == "foo"')
    dep1.activate()
    dep2 = dependency_from_pep_508('B (>=2.0); extra == "bar"')
    dep2.activate()

    package.add_dependency("A", {"version": "^1.0", "extras": ["foo", "bar"]})

    package_a = get_package("A", "1.0.0")
    package_a.extras = {"foo": [dep1], "bar": [dep2]}
    package_a.requires.append(dep1)
    package_a.requires.append(dep2)

    package_b2 = get_package("B", "2.0.0")
    package_b1 = get_package("B", "1.0.0")

    repo.add_package(package_a)
    repo.add_package(package_b1)
    repo.add_package(package_b2)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_b2},
            {"job": "install", "package": package_a},
        ],
    )

    assert str(ops[0].package.marker) == ""
    assert str(ops[1].package.marker) == ""


def test_solver_does_not_raise_conflict_for_locked_conditional_dependencies(
    solver, repo, package
):
    solver.provider.set_package_python_versions("~2.7 || ^3.4")
    package.add_dependency("A", {"version": "^1.0", "python": "^3.6"})
    package.add_dependency("B", "^1.0")

    package_a = get_package("A", "1.0.0")
    package_a.python_versions = ">=3.6"
    package_a.marker = parse_marker(
        'python_version >= "3.6" and python_version < "4.0"'
    )

    package_b = get_package("B", "1.0.0")

    repo.add_package(package_a)
    repo.add_package(package_b)

    solver._locked = Repository([package_a])
    ops = solver.solve(use_latest=[package_b.name])

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_b},
        ],
    )


def test_solver_returns_extras_if_requested_in_dependencies_and_not_in_root_package(
    solver, repo, package
):
    package.add_dependency("A")
    package.add_dependency("B")
    package.add_dependency("C")

    package_a = get_package("A", "1.0")
    package_b = get_package("B", "1.0")
    package_c = get_package("C", "1.0")
    package_d = get_package("D", "1.0")

    package_b.add_dependency("C", {"version": "^1.0", "extras": ["foo"]})

    package_c.add_dependency("D", {"version": "^1.0", "optional": True})
    package_c.extras = {"foo": [get_dependency("D", "^1.0")]}

    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)
    repo.add_package(package_d)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_d},
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_b},
        ],
    )


def test_solver_should_not_resolve_prerelease_version_if_not_requested(
    solver, repo, package
):
    package.add_dependency("A", "~1.8.0")
    package.add_dependency("B", "^0.5.0")

    package_a185 = get_package("A", "1.8.5")
    package_a19b1 = get_package("A", "1.9b1")
    package_b = get_package("B", "0.5.0")
    package_b.add_dependency("A", ">=1.9b1")

    repo.add_package(package_a185)
    repo.add_package(package_a19b1)
    repo.add_package(package_b)

    with pytest.raises(SolverProblemError):
        solver.solve()


def test_solver_ignores_dependencies_with_incompatible_python_full_version_marker(
    solver, repo, package
):
    solver.provider.set_package_python_versions("^3.6")
    package.add_dependency("A", "^1.0")
    package.add_dependency("B", "^2.0")

    package_a = get_package("A", "1.0.0")
    package_a.requires.append(
        dependency_from_pep_508(
            'B (<2.0); platform_python_implementation == "PyPy" and python_full_version < "2.7.9"'
        )
    )

    package_b200 = get_package("B", "2.0.0")
    package_b100 = get_package("B", "1.0.0")

    repo.add_package(package_a)
    repo.add_package(package_b100)
    repo.add_package(package_b200)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_a},
            {"job": "install", "package": package_b200},
        ],
    )


def test_solver_git_dependencies_update(solver, repo, package, installed):
    pendulum = get_package("pendulum", "2.0.3")
    cleo = get_package("cleo", "1.0.0")
    repo.add_package(pendulum)
    repo.add_package(cleo)

    demo_installed = get_package("demo", "0.1.2")
    demo_installed.source_type = "git"
    demo_installed.source_url = "https://github.com/demo/demo.git"
    demo_installed.source_reference = "123456"
    installed.add_package(demo_installed)

    package.add_dependency("demo", {"git": "https://github.com/demo/demo.git"})

    ops = solver.solve()

    demo = get_package("demo", "0.1.2")
    demo.source_type = "git"
    demo.source_url = "https://github.com/demo/demo.git"
    demo.source_reference = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"

    check_solver_result(
        ops,
        [
            {"job": "install", "package": pendulum},
            {"job": "update", "from": demo_installed, "to": demo},
        ],
    )

    op = ops[1]

    assert op.job_type == "update"
    assert op.package.source_type == "git"
    assert op.package.source_reference.startswith("9cf87a2")
    assert op.initial_package.source_reference == "123456"


def test_solver_git_dependencies_update_skipped(solver, repo, package, installed):
    pendulum = get_package("pendulum", "2.0.3")
    cleo = get_package("cleo", "1.0.0")
    repo.add_package(pendulum)
    repo.add_package(cleo)

    demo = get_package("demo", "0.1.2")
    demo.source_type = "git"
    demo.source_url = "https://github.com/demo/demo.git"
    demo.source_reference = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"
    installed.add_package(demo)

    package.add_dependency("demo", {"git": "https://github.com/demo/demo.git"})

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": pendulum},
            {"job": "install", "package": demo, "skipped": True},
        ],
    )


def test_solver_git_dependencies_short_hash_update_skipped(
    solver, repo, package, installed
):
    pendulum = get_package("pendulum", "2.0.3")
    cleo = get_package("cleo", "1.0.0")
    repo.add_package(pendulum)
    repo.add_package(cleo)

    demo = get_package("demo", "0.1.2")
    demo.source_type = "git"
    demo.source_url = "https://github.com/demo/demo.git"
    demo.source_reference = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"
    installed.add_package(demo)

    package.add_dependency(
        "demo", {"git": "https://github.com/demo/demo.git", "rev": "9cf87a2"}
    )

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": pendulum},
            {"job": "install", "package": demo, "skipped": True},
        ],
    )


def test_solver_can_resolve_directory_dependencies(solver, repo, package):
    pendulum = get_package("pendulum", "2.0.3")
    repo.add_package(pendulum)

    path = (
        Path(__file__).parent.parent
        / "fixtures"
        / "git"
        / "github.com"
        / "demo"
        / "demo"
    ).as_posix()

    package.add_dependency("demo", {"path": path})

    ops = solver.solve()

    demo = get_package("demo", "0.1.2")
    demo.source_type = "directory"
    demo.source_url = path

    check_solver_result(
        ops,
        [{"job": "install", "package": pendulum}, {"job": "install", "package": demo}],
    )

    op = ops[1]

    assert op.package.name == "demo"
    assert op.package.version.text == "0.1.2"
    assert op.package.source_type == "directory"
    assert op.package.source_url == path


def test_solver_can_resolve_directory_dependencies_with_extras(solver, repo, package):
    pendulum = get_package("pendulum", "2.0.3")
    cleo = get_package("cleo", "1.0.0")
    repo.add_package(pendulum)
    repo.add_package(cleo)

    path = (
        Path(__file__).parent.parent
        / "fixtures"
        / "git"
        / "github.com"
        / "demo"
        / "demo"
    ).as_posix()

    package.add_dependency("demo", {"path": path, "extras": ["foo"]})

    ops = solver.solve()

    demo = get_package("demo", "0.1.2")
    demo.source_type = "directory"
    demo.source_url = path

    check_solver_result(
        ops,
        [
            {"job": "install", "package": cleo},
            {"job": "install", "package": pendulum},
            {"job": "install", "package": demo},
        ],
    )

    op = ops[2]

    assert op.package.name == "demo"
    assert op.package.version.text == "0.1.2"
    assert op.package.source_type == "directory"
    assert op.package.source_url == path


def test_solver_can_resolve_sdist_dependencies(solver, repo, package):
    pendulum = get_package("pendulum", "2.0.3")
    repo.add_package(pendulum)

    path = (
        Path(__file__).parent.parent
        / "fixtures"
        / "distributions"
        / "demo-0.1.0.tar.gz"
    ).as_posix()

    package.add_dependency("demo", {"path": path})

    ops = solver.solve()

    demo = get_package("demo", "0.1.0")
    demo.source_type = "file"
    demo.source_url = path

    check_solver_result(
        ops,
        [{"job": "install", "package": pendulum}, {"job": "install", "package": demo}],
    )

    op = ops[1]

    assert op.package.name == "demo"
    assert op.package.version.text == "0.1.0"
    assert op.package.source_type == "file"
    assert op.package.source_url == path


def test_solver_can_resolve_sdist_dependencies_with_extras(solver, repo, package):
    pendulum = get_package("pendulum", "2.0.3")
    cleo = get_package("cleo", "1.0.0")
    repo.add_package(pendulum)
    repo.add_package(cleo)

    path = (
        Path(__file__).parent.parent
        / "fixtures"
        / "distributions"
        / "demo-0.1.0.tar.gz"
    ).as_posix()

    package.add_dependency("demo", {"path": path, "extras": ["foo"]})

    ops = solver.solve()

    demo = get_package("demo", "0.1.0")
    demo.source_type = "file"
    demo.source_url = path

    check_solver_result(
        ops,
        [
            {"job": "install", "package": cleo},
            {"job": "install", "package": pendulum},
            {"job": "install", "package": demo},
        ],
    )

    op = ops[2]

    assert op.package.name == "demo"
    assert op.package.version.text == "0.1.0"
    assert op.package.source_type == "file"
    assert op.package.source_url == path


def test_solver_can_resolve_wheel_dependencies(solver, repo, package):
    pendulum = get_package("pendulum", "2.0.3")
    repo.add_package(pendulum)

    path = (
        Path(__file__).parent.parent
        / "fixtures"
        / "distributions"
        / "demo-0.1.0-py2.py3-none-any.whl"
    ).as_posix()

    package.add_dependency("demo", {"path": path})

    ops = solver.solve()

    demo = get_package("demo", "0.1.0")
    demo.source_type = "file"
    demo.source_url = path

    check_solver_result(
        ops,
        [{"job": "install", "package": pendulum}, {"job": "install", "package": demo}],
    )

    op = ops[1]

    assert op.package.name == "demo"
    assert op.package.version.text == "0.1.0"
    assert op.package.source_type == "file"
    assert op.package.source_url == path


def test_solver_can_resolve_wheel_dependencies_with_extras(solver, repo, package):
    pendulum = get_package("pendulum", "2.0.3")
    cleo = get_package("cleo", "1.0.0")
    repo.add_package(pendulum)
    repo.add_package(cleo)

    path = (
        Path(__file__).parent.parent
        / "fixtures"
        / "distributions"
        / "demo-0.1.0-py2.py3-none-any.whl"
    ).as_posix()

    package.add_dependency("demo", {"path": path, "extras": ["foo"]})

    ops = solver.solve()

    demo = get_package("demo", "0.1.0")
    demo.source_type = "file"
    demo.source_url = path

    check_solver_result(
        ops,
        [
            {"job": "install", "package": cleo},
            {"job": "install", "package": pendulum},
            {"job": "install", "package": demo},
        ],
    )

    op = ops[2]

    assert op.package.name == "demo"
    assert op.package.version.text == "0.1.0"
    assert op.package.source_type == "file"
    assert op.package.source_url == path


def test_solver_can_solve_with_legacy_repository_using_proper_dists(
    package, installed, locked, io
):
    repo = MockLegacyRepository()
    pool = Pool([repo])

    solver = Solver(package, pool, installed, locked, io)

    package.add_dependency("isort", "4.3.4")

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": get_package("futures", "3.2.0")},
            {"job": "install", "package": get_package("isort", "4.3.4")},
        ],
    )

    futures = ops[0].package
    assert futures.python_versions == ">=2.6, <3"


def test_solver_can_solve_with_legacy_repository_using_proper_python_compatible_dists(
    package, installed, locked, io
):
    package.python_versions = "^3.7"

    repo = MockLegacyRepository()
    pool = Pool([repo])

    solver = Solver(package, pool, installed, locked, io)

    package.add_dependency("isort", "4.3.4")

    ops = solver.solve()

    check_solver_result(
        ops, [{"job": "install", "package": get_package("isort", "4.3.4")}]
    )


def test_solver_skips_invalid_versions(package, installed, locked, io):
    package.python_versions = "^3.7"

    repo = MockPyPIRepository()
    pool = Pool([repo])

    solver = Solver(package, pool, installed, locked, io)

    package.add_dependency("trackpy", "^0.4")

    ops = solver.solve()

    check_solver_result(
        ops, [{"job": "install", "package": get_package("trackpy", "0.4.1")}]
    )


def test_multiple_constraints_on_root(package, solver, repo):
    package.add_dependency("foo", {"version": "^1.0", "python": "^2.7"})
    package.add_dependency("foo", {"version": "^2.0", "python": "^3.7"})

    foo15 = get_package("foo", "1.5.0")
    foo25 = get_package("foo", "2.5.0")

    repo.add_package(foo15)
    repo.add_package(foo25)

    ops = solver.solve()

    check_solver_result(
        ops,
        [{"job": "install", "package": foo15}, {"job": "install", "package": foo25}],
    )


def test_solver_chooses_most_recent_version_amongst_repositories(
    package, installed, locked, io
):
    package.python_versions = "^3.7"
    package.add_dependency("tomlkit", {"version": "^0.5"})

    repo = MockLegacyRepository()
    pool = Pool([repo, MockPyPIRepository()])

    solver = Solver(package, pool, installed, locked, io)

    ops = solver.solve()

    check_solver_result(
        ops, [{"job": "install", "package": get_package("tomlkit", "0.5.3")}]
    )

    assert "" == ops[0].package.source_type
    assert "" == ops[0].package.source_url


def test_solver_chooses_from_correct_repository_if_forced(
    package, installed, locked, io
):
    package.python_versions = "^3.7"
    package.add_dependency("tomlkit", {"version": "^0.5", "source": "legacy"})

    repo = MockLegacyRepository()
    pool = Pool([repo, MockPyPIRepository()])

    solver = Solver(package, pool, installed, locked, io)

    ops = solver.solve()

    check_solver_result(
        ops, [{"job": "install", "package": get_package("tomlkit", "0.5.2")}]
    )

    assert "http://legacy.foo.bar" == ops[0].package.source_url


def test_solver_chooses_from_correct_repository_if_forced_and_transitive_dependency(
    package, installed, locked, io
):
    package.python_versions = "^3.7"
    package.add_dependency("foo", "^1.0")
    package.add_dependency("tomlkit", {"version": "^0.5", "source": "legacy"})

    repo = Repository()
    foo = get_package("foo", "1.0.0")
    foo.add_dependency("tomlkit", "^0.5.0")
    repo.add_package(foo)
    pool = Pool([MockLegacyRepository(), repo, MockPyPIRepository()])

    solver = Solver(package, pool, installed, locked, io)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": get_package("tomlkit", "0.5.2")},
            {"job": "install", "package": foo},
        ],
    )

    assert "http://legacy.foo.bar" == ops[0].package.source_url

    assert "" == ops[1].package.source_type
    assert "" == ops[1].package.source_url


def test_solver_does_not_choose_from_secondary_repository_by_default(
    package, installed, locked, io
):
    package.python_versions = "^3.7"
    package.add_dependency("clikit", {"version": "^0.2.0"})

    pool = Pool()
    pool.add_repository(MockPyPIRepository(), secondary=True)
    pool.add_repository(MockLegacyRepository())

    solver = Solver(package, pool, installed, locked, io)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": get_package("pastel", "0.1.0")},
            {"job": "install", "package": get_package("pylev", "1.3.0")},
            {"job": "install", "package": get_package("clikit", "0.2.4")},
        ],
    )

    assert "http://legacy.foo.bar" == ops[0].package.source_url
    assert "" == ops[1].package.source_type
    assert "" == ops[1].package.source_url
    assert "http://legacy.foo.bar" == ops[2].package.source_url


def test_solver_chooses_from_secondary_if_explicit(package, installed, locked, io):
    package.python_versions = "^3.7"
    package.add_dependency("clikit", {"version": "^0.2.0", "source": "PyPI"})

    pool = Pool()
    pool.add_repository(MockPyPIRepository(), secondary=True)
    pool.add_repository(MockLegacyRepository())

    solver = Solver(package, pool, installed, locked, io)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": get_package("pastel", "0.1.0")},
            {"job": "install", "package": get_package("pylev", "1.3.0")},
            {"job": "install", "package": get_package("clikit", "0.2.4")},
        ],
    )

    assert "http://legacy.foo.bar" == ops[0].package.source_url
    assert "" == ops[1].package.source_type
    assert "" == ops[1].package.source_url
    assert "" == ops[2].package.source_type
    assert "" == ops[2].package.source_url


def test_solver_discards_packages_with_empty_markers(
    package, installed, locked, io, pool, repo
):
    package.python_versions = "~2.7 || ^3.4"
    package.add_dependency(
        "a", {"version": "^0.1.0", "markers": "python_version >= '3.4'"}
    )

    package_a = get_package("a", "0.1.0")
    package_a.add_dependency(
        "b", {"version": "^0.1.0", "markers": "python_version < '3.2'"}
    )
    package_a.add_dependency("c", "^0.2.0")
    package_b = get_package("b", "0.1.0")
    package_c = get_package("c", "0.2.0")
    repo.add_package(package_a)
    repo.add_package(package_b)
    repo.add_package(package_c)

    solver = Solver(package, pool, installed, locked, io)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_c},
            {"job": "install", "package": package_a},
        ],
    )


def test_solver_does_not_raise_conflict_for_conditional_dev_dependencies(
    solver, repo, package
):
    solver.provider.set_package_python_versions("~2.7 || ^3.5")
    package.add_dependency("A", {"version": "^1.0", "python": "~2.7"}, category="dev")
    package.add_dependency("A", {"version": "^2.0", "python": "^3.5"}, category="dev")

    package_a100 = get_package("A", "1.0.0")
    package_a200 = get_package("A", "2.0.0")

    repo.add_package(package_a100)
    repo.add_package(package_a200)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_a100},
            {"job": "install", "package": package_a200},
        ],
    )


def test_solver_does_not_loop_indefinitely_on_duplicate_constraints_with_extras(
    solver, repo, package
):
    solver.provider.set_package_python_versions("~2.7 || ^3.5")
    package.add_dependency("requests", {"version": "^2.22.0", "extras": ["security"]})

    requests = get_package("requests", "2.22.0")
    requests.add_dependency("idna", ">=2.5,<2.9")
    requests.add_dependency(
        "idna", {"version": ">=2.0.0", "markers": "extra == 'security'"}
    )
    requests.extras["security"] = [get_dependency("idna", ">=2.0.0")]
    idna = get_package("idna", "2.8")

    repo.add_package(requests)
    repo.add_package(idna)

    ops = solver.solve()

    check_solver_result(
        ops,
        [{"job": "install", "package": idna}, {"job": "install", "package": requests}],
    )


def test_solver_does_not_fail_with_locked_git_and_non_git_dependencies(
    solver, repo, package, locked, pool, installed, io
):
    package.add_dependency("demo", {"git": "https://github.com/demo/demo.git"})
    package.add_dependency("a", "^1.2.3")

    git_package = get_package("demo", "0.1.2")
    git_package.source_type = "git"
    git_package.source_url = "https://github.com/demo/demo.git"
    git_package.source_reference = "commit"

    installed.add_package(git_package)

    locked.add_package(get_package("a", "1.2.3"))
    locked.add_package(git_package)

    repo.add_package(get_package("a", "1.2.3"))

    solver = Solver(package, pool, installed, locked, io)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": get_package("a", "1.2.3")},
            {"job": "install", "package": git_package, "skipped": True},
        ],
    )


def test_ignore_python_constraint_no_overlap_dependencies(solver, repo, package):
    pytest = get_package("demo", "1.0.0")
    pytest.add_dependency("configparser", {"version": "^1.2.3", "python": "<3.2"})

    package.add_dependency("demo", {"version": "^1.0.0", "python": "^3.6"})

    repo.add_package(pytest)
    repo.add_package(get_package("configparser", "1.2.3"))

    ops = solver.solve()

    check_solver_result(
        ops, [{"job": "install", "package": pytest}],
    )


def test_solver_should_not_go_into_an_infinite_loop_on_duplicate_dependencies(
    solver, repo, package
):
    solver.provider.set_package_python_versions("~2.7 || ^3.5")
    package.add_dependency("A", "^1.0")

    package_a = get_package("A", "1.0.0")
    package_a.add_dependency("B")
    package_a.add_dependency(
        "B", {"version": "^1.0", "markers": "implementation_name == 'pypy'"}
    )

    package_b20 = get_package("B", "2.0.0")
    package_b10 = get_package("B", "1.0.0")

    repo.add_package(package_a)
    repo.add_package(package_b10)
    repo.add_package(package_b20)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_b10},
            {"job": "install", "package": package_b20},
            {"job": "install", "package": package_a},
        ],
    )


def test_solver_remove_untracked_single(package, pool, installed, locked, io):
    solver = Solver(package, pool, installed, locked, io, remove_untracked=True)
    package_a = get_package("a", "1.0")
    installed.add_package(package_a)

    ops = solver.solve()

    check_solver_result(ops, [{"job": "remove", "package": package_a}])


def test_solver_remove_untracked_keeps_critical_package(
    package, pool, installed, locked, io
):
    solver = Solver(package, pool, installed, locked, io, remove_untracked=True)
    package_pip = get_package("pip", "1.0")
    installed.add_package(package_pip)

    ops = solver.solve()

    check_solver_result(ops, [])


def test_solver_cannot_choose_another_version_for_directory_dependencies(
    solver, repo, package
):
    pendulum = get_package("pendulum", "2.0.3")
    demo = get_package("demo", "0.1.0")
    foo = get_package("foo", "1.2.3")
    foo.add_dependency("demo", "<0.1.2")
    repo.add_package(foo)
    repo.add_package(demo)
    repo.add_package(pendulum)

    path = (
        Path(__file__).parent.parent
        / "fixtures"
        / "git"
        / "github.com"
        / "demo"
        / "demo"
    ).as_posix()

    package.add_dependency("demo", {"path": path})
    package.add_dependency("foo", "^1.2.3")

    # This is not solvable since the demo version is pinned
    # via the directory dependency
    with pytest.raises(SolverProblemError):
        solver.solve()


def test_solver_cannot_choose_another_version_for_file_dependencies(
    solver, repo, package
):
    pendulum = get_package("pendulum", "2.0.3")
    demo = get_package("demo", "0.0.8")
    foo = get_package("foo", "1.2.3")
    foo.add_dependency("demo", "<0.1.0")
    repo.add_package(foo)
    repo.add_package(demo)
    repo.add_package(pendulum)

    path = (
        Path(__file__).parent.parent
        / "fixtures"
        / "distributions"
        / "demo-0.1.0-py2.py3-none-any.whl"
    ).as_posix()

    package.add_dependency("demo", {"path": path})
    package.add_dependency("foo", "^1.2.3")

    # This is not solvable since the demo version is pinned
    # via the file dependency
    with pytest.raises(SolverProblemError):
        solver.solve()


def test_solver_cannot_choose_another_version_for_git_dependencies(
    solver, repo, package
):
    pendulum = get_package("pendulum", "2.0.3")
    demo = get_package("demo", "0.0.8")
    foo = get_package("foo", "1.2.3")
    foo.add_dependency("demo", "<0.1.0")
    repo.add_package(foo)
    repo.add_package(demo)
    repo.add_package(pendulum)

    package.add_dependency("demo", {"git": "https://github.com/demo/demo.git"})
    package.add_dependency("foo", "^1.2.3")

    # This is not solvable since the demo version is pinned
    # via the file dependency
    with pytest.raises(SolverProblemError):
        solver.solve()


def test_solver_cannot_choose_another_version_for_url_dependencies(
    solver, repo, package, http
):
    path = (
        Path(__file__).parent.parent
        / "fixtures"
        / "distributions"
        / "demo-0.1.0-py2.py3-none-any.whl"
    )

    http.register_uri(
        "GET",
        "https://foo.bar/demo-0.1.0-py2.py3-none-any.whl",
        body=path.read_bytes(),
        streaming=True,
    )
    pendulum = get_package("pendulum", "2.0.3")
    demo = get_package("demo", "0.0.8")
    foo = get_package("foo", "1.2.3")
    foo.add_dependency("demo", "<0.1.0")
    repo.add_package(foo)
    repo.add_package(demo)
    repo.add_package(pendulum)

    package.add_dependency(
        "demo", {"url": "https://foo.bar/distributions/demo-0.1.0-py2.py3-none-any.whl"}
    )
    package.add_dependency("foo", "^1.2.3")

    # This is not solvable since the demo version is pinned
    # via the git dependency
    with pytest.raises(SolverProblemError):
        solver.solve()


def test_solver_should_not_update_same_version_packages_if_installed_has_no_source_type(
    solver, repo, package, installed
):
    package.add_dependency("foo", "1.0.0")

    foo = get_package("foo", "1.0.0")
    foo.source_type = "legacy"
    foo.source_reference = "custom"
    foo.source_url = "https://foo.bar"
    repo.add_package(foo)
    installed.add_package(get_package("foo", "1.0.0"))

    ops = solver.solve()

    check_solver_result(ops, [{"job": "install", "package": foo, "skipped": True}])


def test_solver_should_use_the_python_constraint_from_the_environment_if_available(
    solver, repo, package, installed
):
    solver.provider.set_package_python_versions("~2.7 || ^3.5")
    package.add_dependency("A", "^1.0")

    a = get_package("A", "1.0.0")
    a.add_dependency("B", {"version": "^1.0.0", "markers": 'python_version < "3.2"'})
    b = get_package("B", "1.0.0")
    b.python_versions = ">=2.6, <3"

    repo.add_package(a)
    repo.add_package(b)

    with solver.use_environment(MockEnv((2, 7, 18))):
        ops = solver.solve()

    check_solver_result(
        ops, [{"job": "install", "package": b}, {"job": "install", "package": a}],
    )


def test_solver_should_resolve_all_versions_for_multiple_duplicate_dependencies(
    solver, repo, package
):
    package.python_versions = "~2.7 || ^3.5"
    package.add_dependency(
        "A", {"version": "^1.0", "markers": "python_version < '3.5'"}
    )
    package.add_dependency(
        "A", {"version": "^2.0", "markers": "python_version >= '3.5'"}
    )
    package.add_dependency(
        "B", {"version": "^3.0", "markers": "python_version < '3.5'"}
    )
    package.add_dependency(
        "B", {"version": "^4.0", "markers": "python_version >= '3.5'"}
    )

    package_a10 = get_package("A", "1.0.0")
    package_a20 = get_package("A", "2.0.0")
    package_b30 = get_package("B", "3.0.0")
    package_b40 = get_package("B", "4.0.0")

    repo.add_package(package_a10)
    repo.add_package(package_a20)
    repo.add_package(package_b30)
    repo.add_package(package_b40)

    ops = solver.solve()

    check_solver_result(
        ops,
        [
            {"job": "install", "package": package_a10},
            {"job": "install", "package": package_a20},
            {"job": "install", "package": package_b30},
            {"job": "install", "package": package_b40},
        ],
    )


def test_solver_should_not_raise_errors_for_irrelevant_python_constraints(
    solver, repo, package
):
    package.python_versions = "^3.6"
    solver.provider.set_package_python_versions("^3.6")
    package.add_dependency("dataclasses", {"version": "^0.7", "python": "<3.7"})

    dataclasses = get_package("dataclasses", "0.7")
    dataclasses.python_versions = ">=3.6, <3.7"

    repo.add_package(dataclasses)
    ops = solver.solve()

    check_solver_result(ops, [{"job": "install", "package": dataclasses}])
