"""Command line interface for Revo modules."""

from __future__ import annotations

from pathlib import Path
from typing import Annotated

import typer

from revo_module_installer import __version__
from revo_module_installer.cli_support import flows as cli_flows, helpers as cli_helpers
from revo_module_installer.config.load import (
    get_local_example_registry_path,
    is_local_registry_environment,
    load_registry_index,
)
from revo_module_installer.config.manifest import ManifestIndex
from revo_module_installer.config.registry import RegistryEntry, RegistryIndex
from revo_module_installer.config.settings import get_default_registry_url
from revo_module_installer.logging import setup_logging

app: typer.Typer = typer.Typer(help="Revo modules CLI.")
modules_app: typer.Typer = typer.Typer(help="Manage modules.")
app.add_typer(modules_app, name="modules")
console = cli_flows.console
InstallPlan = cli_helpers.InstallPlan
build_registry_table = cli_helpers.build_registry_table
build_search_table = cli_helpers.build_search_table
build_install_plan = cli_helpers.build_install_plan
confirm_module_installation = cli_helpers.confirm_module_installation
default_plan_path = cli_helpers.default_plan_path
filter_registry_entries = cli_helpers.filter_registry_entries
find_registry_entry = cli_helpers.find_registry_entry
find_registry_entry_by_name = cli_helpers.find_registry_entry_by_name
format_search_results = cli_helpers.format_search_results
prompt_for_module_name = cli_helpers.prompt_for_module_name
prompt_for_search_query = cli_helpers.prompt_for_search_query
prompt_for_variant_id = cli_helpers.prompt_for_variant_id
render_manifest_table = cli_helpers.render_manifest_table
resolve_manifest_index = cli_helpers.resolve_manifest_index
resolve_manifest_source_url = cli_helpers.resolve_manifest_source_url
execute_module_install = cli_flows.execute_module_install
run_default_module_install = cli_flows.run_default_module_install


def resolve_local_registry_fallback(allow_local_fallback: bool) -> bool:
    """Determine whether local registry fallback is allowed.

    Args:
        allow_local_fallback: Whether the caller wants to allow local usage.

    Returns:
        True when local fallback is enabled or the local environment is active.
    """
    return allow_local_fallback or is_local_registry_environment()


def main_callback(verbose: bool = False) -> None:
    """Configure logging for CLI commands.

    Args:
        verbose: Enable debug logging.
    """
    setup_logging(verbose)


def version_callback(show_version: bool) -> None:
    """Handle the CLI version flag.

    Args:
        show_version: Whether the caller requested the version output.
    """
    if not show_version:
        return
    typer.echo(__version__)
    raise typer.Exit()


@app.callback(invoke_without_command=True)
def main(
    context: typer.Context,
    verbose: Annotated[bool, typer.Option("--verbose", "-v")] = False,
    version: Annotated[
        bool,
        typer.Option(
            "--version",
            help="Show the version and exit.",
            callback=version_callback,
            is_eager=True,
        ),
    ] = False,
) -> None:
    """Revo modules CLI entrypoint.

    Args:
        context: Typer context for the current command.
        verbose: Enable debug logging.
        version: Output the current version and exit.
    """
    main_callback(verbose)
    if context.invoked_subcommand is not None:
        return
    run_default_module_install()


@modules_app.command("list")
def list_modules(
    registry_url: Annotated[
        str | None,
        typer.Option(
            "--registry-url",
            help="Registry repository or index URL.",
        ),
    ] = None,
    show_description: Annotated[
        bool,
        typer.Option(
            "--show-description",
            help="Show module descriptions in the table.",
        ),
    ] = False,
    allow_local_fallback: Annotated[
        bool,
        typer.Option(
            "--allow-local-fallback",
            help="Allow using the bundled example registry in tests.",
        ),
    ] = False,
    local_registry_path: Annotated[
        Path | None,
        typer.Option(
            "--local-registry-path",
            help="Override path to the bundled example registry file.",
        ),
    ] = None,
) -> None:
    """List registry modules."""
    effective_registry_url: str = registry_url or get_default_registry_url()
    effective_fallback: bool = resolve_local_registry_fallback(
        allow_local_fallback,
    )
    registry_index: RegistryIndex = load_registry_index(
        effective_registry_url,
        allow_local_fallback=effective_fallback,
        local_registry_path=local_registry_path,
    )
    entry_count: int = len(registry_index.entries)
    console.print(f"Registry entries: {entry_count}")
    console.print(
        build_registry_table(
            registry_index.entries,
            show_description=show_description,
        ),
    )


@modules_app.command("install")
def install_modules(
    module_id: Annotated[
        str,
        typer.Argument(help="Module ID to install."),
    ],
    registry_url: Annotated[
        str | None,
        typer.Option(
            "--registry-url",
            help="Registry repository or index URL.",
        ),
    ] = None,
    manifest_url: Annotated[
        str | None,
        typer.Option(
            "--manifest-url",
            help="Override manifest URL instead of registry metadata.",
        ),
    ] = None,
    allow_local_fallback: Annotated[
        bool,
        typer.Option(
            "--allow-local-fallback",
            help="Allow using the bundled example registry in tests.",
        ),
    ] = False,
    local_registry_path: Annotated[
        Path | None,
        typer.Option(
            "--local-registry-path",
            help="Override path to the bundled example registry file.",
        ),
    ] = None,
    dry_run: Annotated[
        bool,
        typer.Option(
            "--dry-run",
            help="Validate and display actions without writing output.",
        ),
    ] = False,
) -> None:
    """Install modules by executing manifest actions."""
    effective_registry_url: str = registry_url or get_default_registry_url()
    effective_fallback: bool = resolve_local_registry_fallback(
        allow_local_fallback,
    )
    registry_index: RegistryIndex = load_registry_index(
        effective_registry_url,
        allow_local_fallback=effective_fallback,
        local_registry_path=local_registry_path,
    )
    registry_entry: RegistryEntry | None = find_registry_entry(
        registry_index.entries,
        module_id,
    )
    if registry_entry is None:
        local_example_registry_path: Path = get_local_example_registry_path()
        local_registry_index: RegistryIndex = load_registry_index(
            str(local_example_registry_path),
        )
        local_registry_entry: RegistryEntry | None = find_registry_entry(
            local_registry_index.entries,
            module_id,
        )
        if local_registry_entry is None:
            console.print(f"[red]Module not found:[/] {module_id}")
            raise typer.Exit(code=1)
        console.print(
            "[yellow]Module not found in remote registry; "
            "using the bundled example registry.[/]",
        )
        registry_entry = local_registry_entry
        registry_index = local_registry_index

    selected_variant_id: str | None = prompt_for_variant_id(registry_entry)
    manifest_source_url: str = resolve_manifest_source_url(
        registry_entry,
        manifest_url,
        selected_variant_id=selected_variant_id,
    )
    manifest_index: ManifestIndex = resolve_manifest_index(
        registry_entry,
        manifest_url_override=manifest_url,
        selected_variant_id=selected_variant_id,
    )
    execute_module_install(
        registry_entry,
        registry_index,
        manifest_index,
        manifest_source_url,
        dry_run=dry_run,
    )
