import sys

from pipenv.routines.install import do_sync
from pipenv.routines.lock import do_lock
from pipenv.routines.outdated import do_outdated
from pipenv.utils.dependencies import (
    convert_deps_to_pip,
    get_pipfile_category_using_lockfile_section,
    is_star,
    pep423_name,
)
from pipenv.utils.project import ensure_project
from pipenv.utils.resolver import venv_resolve_deps
from pipenv.vendor import click
from pipenv.vendor.requirementslib.models.requirements import Requirement


def do_update(
    project,
    python=None,
    pre=False,
    system=False,
    packages=None,
    editable_packages=None,
    site_packages=False,
    pypi_mirror=None,
    dev=False,
    categories=None,
    extra_pip_args=None,
    quiet=False,
    bare=False,
    dry_run=None,
    outdated=False,
    keep_outdated=False,
    clear=False,
    lock_only=False,
):
    ensure_project(
        project,
        python=python,
        pypi_mirror=pypi_mirror,
        warn=(not quiet),
        site_packages=site_packages,
        clear=clear,
    )
    if not outdated:
        outdated = bool(dry_run)
    if outdated:
        do_outdated(
            project,
            clear=clear,
            pre=pre,
            pypi_mirror=pypi_mirror,
        )
    packages = [p for p in packages if p]
    editable = [p for p in editable_packages if p]
    if not packages:
        click.echo(
            "{} {} {} {}{}".format(
                click.style("Running", bold=True),
                click.style("$ pipenv lock", fg="yellow", bold=True),
                click.style("then", bold=True),
                click.style("$ pipenv sync", fg="yellow", bold=True),
                click.style(".", bold=True),
            )
        )
        do_lock(
            project,
            clear=clear,
            pre=pre,
            keep_outdated=keep_outdated,
            pypi_mirror=pypi_mirror,
            write=not quiet,
        )
    else:
        upgrade(
            project,
            pre=pre,
            system=system,
            packages=packages,
            editable_packages=editable,
            pypi_mirror=pypi_mirror,
            categories=categories,
            dev=dev,
            lock_only=lock_only,
        )

    do_sync(
        project,
        dev=dev,
        categories=categories,
        python=python,
        bare=bare,
        dont_upgrade=not keep_outdated,
        user=False,
        clear=clear,
        unused=False,
        pypi_mirror=pypi_mirror,
        extra_pip_args=extra_pip_args,
    )


def upgrade(
    project,
    pre=False,
    system=False,
    packages=None,
    editable_packages=None,
    pypi_mirror=None,
    categories=None,
    dev=False,
    lock_only=False,
):
    lockfile = project._lockfile()
    if not pre:
        pre = project.settings.get("allow_prereleases")
    if dev:
        categories = ["develop"]
    elif not categories:
        categories = ["default"]

    package_args = [p for p in packages] + [f"-e {pkg}" for pkg in editable_packages]

    reqs = {}
    requested_packages = {}
    for package in package_args[:]:
        # section = project.packages if not dev else project.dev_packages
        section = {}
        package = Requirement.from_line(package)
        package_name, package_val = package.pipfile_entry
        package_name = pep423_name(package_name)
        requested_packages[package_name] = package
        try:
            if not is_star(section[package_name]) and is_star(package_val):
                # Support for VCS dependencies.
                package_val = convert_deps_to_pip(
                    {package_name: section[package_name]}, project=project
                )[0]
        except KeyError:
            pass
        reqs[package_name] = package_val

    if not reqs:
        click.echo("Nothing to upgrade!")
        sys.exit(0)

    # Resolve package to generate constraints of new package data
    upgrade_lock_data = venv_resolve_deps(
        reqs,
        which=project._which,
        project=project,
        lockfile={},
        category="default",
        pre=pre,
        allow_global=system,
        pypi_mirror=pypi_mirror,
        keep_outdated=False,
    )
    if not upgrade_lock_data:
        click.echo("Nothing to upgrade!")
        sys.exit(0)

    # Upgrade the relevant packages in the various categories specified
    for category in categories:
        pipfile_category = get_pipfile_category_using_lockfile_section(category)
        if project.pipfile_exists:
            packages = project.parsed_pipfile.get(pipfile_category, {})
        else:
            packages = project.get_pipfile_section(pipfile_category)
        for package_name, requirement in requested_packages.items():
            requested_package = reqs[package_name]
            if package_name not in packages:
                packages.append(package_name, requested_package)
            else:
                packages[package_name] = requested_package
            if lock_only is False:
                project.add_package_to_pipfile(requirement, category=pipfile_category)

        full_lock_resolution = venv_resolve_deps(
            packages,
            which=project._which,
            project=project,
            lockfile={},
            category=pipfile_category,
            pre=pre,
            allow_global=system,
            pypi_mirror=pypi_mirror,
            keep_outdated=False,
        )
        # Mutate the existing lockfile with the upgrade data for the categories
        for package_name, _ in upgrade_lock_data.items():
            correct_package_lock = full_lock_resolution.get(package_name)
            if correct_package_lock:
                lockfile[category][package_name] = correct_package_lock

    lockfile.update({"_meta": project.get_lockfile_meta()})
    project.write_lockfile(lockfile)
