import click

from hatch.cli.env.lock import dependency_lock_click_options, run_dep_lock


@click.group(short_help="Manage environment dependencies")
def dep():
    pass


@dep.command("lock", short_help="Generate a lockfile for the active environment (`-e` / `HATCH_ENV`)")
@dependency_lock_click_options
@click.pass_obj
def dep_lock(
    app,
    *,
    upgrade: bool,
    upgrade_package: tuple[str, ...],
    export_path: str | None,
    export_all_path: str | None,
    check: bool,
):
    """Resolve dependencies and write a PEP 751 ``pylock.toml`` for the selected environment."""
    app.ensure_environment_plugin_dependencies()
    run_dep_lock(
        app,
        upgrade=upgrade,
        upgrade_package=upgrade_package,
        export_path=export_path,
        export_all_path=export_all_path,
        check=check,
    )


@dep.command("sync", short_help="Install dependencies from the environment lockfile (`apply_lock`)")
@click.pass_obj
def dep_sync(app):
    """Sync the active environment to its lockfile (``locked`` environments only)."""
    app.ensure_environment_plugin_dependencies()

    environment = app.project.get_environment()
    if not environment.locked:
        app.abort(
            "The active environment is not `locked`. Set `locked = true` or use `hatch env lock --export` "
            "and a normal install workflow."
        )

    from hatch.env.lock import LockerNotFoundError, LockerUnsupportedError, resolve_lockfile_path

    lock_path = resolve_lockfile_path(environment)
    if not lock_path.is_file():
        app.abort(f"No lockfile at `{lock_path}`. Run `hatch dep lock` or `hatch env lock` first.")

    try:
        with app.status("Syncing from lockfile"):
            environment.sync_dependencies()
    except LockerNotFoundError as e:
        app.abort(str(e))
    except LockerUnsupportedError as e:
        if e.detail:
            app.abort(f"Cannot sync environment `{environment.name}` from lockfile: {e.detail}")
        app.abort(str(e))

    app.display_success(f"Synced environment `{environment.name}` from `{lock_path.name}`")


@dep.command("hash", short_help="Output a hash of the currently defined dependencies")
@click.option("--project-only", "-p", is_flag=True, help="Whether or not to exclude environment dependencies")
@click.option("--env-only", "-e", is_flag=True, help="Whether or not to exclude project dependencies")
@click.pass_obj
def hash_dependencies(app, project_only, env_only):
    """Output a hash of the currently defined dependencies."""
    app.ensure_environment_plugin_dependencies()

    from hatch.utils.dep import get_complex_dependencies, hash_dependencies

    environment = app.project.get_environment()

    all_requirements = []
    if project_only:
        dependencies, _ = app.project.get_dependencies()
        dependencies_complex = get_complex_dependencies(dependencies)
        all_requirements.extend(dependencies_complex.values())
    elif env_only:
        all_requirements.extend(environment.environment_dependencies_complex)
    else:
        dependencies, _ = app.project.get_dependencies()
        dependencies_complex = get_complex_dependencies(dependencies)
        all_requirements.extend(dependencies_complex.values())
        all_requirements.extend(environment.environment_dependencies_complex)

    app.display(hash_dependencies(all_requirements))


@dep.group(short_help="Display dependencies in various formats")
def show():
    pass


@show.command(short_help="Enumerate dependencies in a tabular format")
@click.option("--project-only", "-p", is_flag=True, help="Whether or not to exclude environment dependencies")
@click.option("--env-only", "-e", is_flag=True, help="Whether or not to exclude project dependencies")
@click.option("--lines", "-l", "show_lines", is_flag=True, help="Whether or not to show lines between table rows")
@click.option("--ascii", "force_ascii", is_flag=True, help="Whether or not to only use ASCII characters")
@click.pass_obj
def table(app, project_only, env_only, show_lines, force_ascii):
    """Enumerate dependencies in a tabular format."""
    app.ensure_environment_plugin_dependencies()

    from hatch.dep.core import Dependency
    from hatch.utils.dep import get_complex_dependencies, get_normalized_dependencies, normalize_marker_quoting

    environment = app.project.get_environment()

    project_requirements = []
    environment_requirements = []
    if project_only:
        dependencies, _ = app.project.get_dependencies()
        dependencies_complex = get_complex_dependencies(dependencies)
        project_requirements.extend(dependencies_complex.values())
    elif env_only:
        environment_requirements.extend(environment.environment_dependencies_complex)
    else:
        dependencies, _ = app.project.get_dependencies()
        dependencies_complex = get_complex_dependencies(dependencies)
        project_requirements.extend(dependencies_complex.values())
        environment_requirements.extend(environment.environment_dependencies_complex)

    for all_requirements, table_title in (
        (project_requirements, "Project"),
        (environment_requirements, f"Env: {app.env}"),
    ):
        if not all_requirements:
            continue

        normalized_requirements = [Dependency(d) for d in get_normalized_dependencies(all_requirements)]

        columns = {"Name": {}, "URL": {}, "Versions": {}, "Markers": {}, "Features": {}}
        for i, requirement in enumerate(normalized_requirements):
            columns["Name"][i] = requirement.name

            if requirement.url:
                columns["URL"][i] = str(requirement.url)

            if requirement.specifier:
                columns["Versions"][i] = str(requirement.specifier)

            if requirement.marker:
                columns["Markers"][i] = normalize_marker_quoting(str(requirement.marker))

            if requirement.extras:
                columns["Features"][i] = ", ".join(sorted(requirement.extras))

        column_options = {}
        for column_title in columns:
            if column_title != "URL":
                column_options[column_title] = {"no_wrap": True}

        app.display_table(
            table_title, columns, show_lines=show_lines, column_options=column_options, force_ascii=force_ascii
        )


@show.command(short_help="Enumerate dependencies as a list of requirements")
@click.option("--project-only", "-p", is_flag=True, help="Whether or not to exclude environment dependencies")
@click.option("--env-only", "-e", is_flag=True, help="Whether or not to exclude project dependencies")
@click.option(
    "--feature",
    "-f",
    "features",
    multiple=True,
    help="Whether or not to only show the dependencies of the specified features",
)
@click.option("--all", "all_features", is_flag=True, help="Whether or not to include the dependencies of all features")
@click.pass_obj
def requirements(app, project_only, env_only, features, all_features):
    """Enumerate dependencies as a list of requirements."""
    app.ensure_environment_plugin_dependencies()

    from hatch.utils.dep import get_complex_dependencies, get_complex_features, get_normalized_dependencies
    from hatchling.metadata.utils import normalize_project_name

    environment = app.project.get_environment()
    dependencies, optional_dependencies = app.project.get_dependencies()
    dependencies_complex = get_complex_dependencies(dependencies)
    optional_dependencies_complex = get_complex_features(optional_dependencies)

    all_requirements = []
    if features:
        for raw_feature in features:
            feature = normalize_project_name(raw_feature)
            if feature not in optional_dependencies_complex:
                app.abort(f"Feature `{feature}` is not defined in field `project.optional-dependencies`")

            all_requirements.extend(optional_dependencies_complex[feature].values())
    elif project_only:
        all_requirements.extend(dependencies_complex.values())
    elif env_only:
        all_requirements.extend(environment.environment_dependencies_complex)
    else:
        all_requirements.extend(dependencies_complex.values())
        all_requirements.extend(environment.environment_dependencies_complex)

    if not features and all_features:
        for optional_dependencies in optional_dependencies_complex.values():
            all_requirements.extend(optional_dependencies.values())

    for dependency in get_normalized_dependencies(all_requirements):
        app.display(dependency)
