"""Main CLI entry point for CleverAgents.

Based on ADR-009: CLI Framework using Typer.
"""

import sys
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Any

import typer

from cleveragents import __version__
from cleveragents.cli.formatting import OutputFormat
from cleveragents.core.exceptions import CleverAgentsError

if TYPE_CHECKING:
    from rich.console import Console

_console: "Console | None" = None
_err_console: "Console | None" = None
_subcommands_registered = False

__all__ = [
    "_print_basic_help",
    "app",
    "cli",
    "ensure_cli_commands_registered",
    "err_console",
    "get_console",
    "get_err_console",
]


def get_console() -> "Console":
    """Lazily create a stdout console."""
    global _console
    if _console is None:
        from rich.console import Console

        _console = Console()
    return _console


def get_err_console() -> "Console":
    """Lazily create a stderr console."""
    global _err_console
    if _err_console is None:
        from rich.console import Console

        _err_console = Console(stderr=True)
    return _err_console


# Expose stderr console for patching/testing convenience.
err_console = get_err_console()


# Create the main Typer app
app: Any = typer.Typer(
    name="cleveragents",
    help=(
        "AI-powered development assistant (actor-first). "
        "Pass --actor <name> or set a default with 'agents actor set-default'. "
        "Built-in actors are named <provider>/<model>; custom actors use local/<id>. "
        "Configs marked unsafe require --unsafe during add/update."
    ),
    add_completion=True,
    pretty_exceptions_enable=True,
    pretty_exceptions_show_locals=False,
    rich_markup_mode="rich",
)


def _register_subcommands() -> None:
    """Register CLI subcommands lazily to reduce startup time."""
    global _subcommands_registered
    if _subcommands_registered:
        return

    try:
        from cleveragents.cli.commands import (
            action,
            actor,
            audit,
            automation_profile,
            cleanup,
            config,
            context,
            invariant,
            lsp,
            plan,
            project,
            repo,
            resource,
            session,
            skill,
            tool,
            tui,
            validation,
        )
        from cleveragents.cli.commands.auto_debug import app as auto_debug_app
        from cleveragents.cli.commands.db import app as db_app
        from cleveragents.cli.commands.repl import _repl_app
        from cleveragents.cli.commands.server import app as server_app
    except Exception as exc:
        import traceback

        err = get_err_console()
        err.print(f"[red]Failed to register subcommands:[/red] {exc}")
        err.print(traceback.format_exc())
        raise SystemExit(1) from exc

    app.add_typer(project.app, name="project", help="Project management")

    # Register context as a sub-app of actor (canonical: agents actor context)
    actor.app.add_typer(
        context.app,
        name="context",
        help="Actor context management commands",
    )

    # Keep top-level 'agents context' as deprecated alias
    app.add_typer(
        context.app,
        name="context",
        help="Context management (deprecated: use 'agents actor context' instead)",
        deprecated=True,
    )
    app.add_typer(
        plan.app,
        name="plan",
        help=(
            "Plan operations (actor required; set default via "
            "'agents actor set-default')"
        ),
    )
    app.add_typer(
        actor.app,
        name="actor",
        help=(
            "Actor management (built-ins <provider>/<model>; customs local/<id>; "
            "unsafe requires --unsafe)"
        ),
    )
    app.add_typer(
        action.app,
        name="action",
        help="Manage actions (reusable plan templates) for the v3 plan lifecycle",
    )
    app.add_typer(auto_debug_app, name="auto-debug", help="Auto-debug operations")
    app.add_typer(
        db_app,
        name="db",
        help="Database migration management (Alembic)",
    )
    app.add_typer(
        resource.app,
        name="resource",
        help="Manage resources and resource types in the resource registry",
    )
    app.add_typer(
        skill.app,
        name="skill",
        help="Manage skills (reusable, namespaced tool collections)",
    )
    app.add_typer(
        lsp.app,
        name="lsp",
        help="Manage LSP (Language Server Protocol) servers in the registry",
    )
    app.add_typer(
        cleanup.app,
        name="cleanup",
        help="Garbage collection and cleanup for stale resources",
    )
    app.add_typer(
        audit.app,
        name="audit",
        help="View and manage the audit log for security-relevant operations",
    )
    app.add_typer(
        session.app,
        name="session",
        help="Manage interactive sessions",
    )
    app.add_typer(
        config.app,
        name="config",
        help="Manage configuration settings for CleverAgents",
    )
    app.add_typer(
        tool.app,
        name="tool",
        help="Manage tools (callable operations) in the tool registry",
    )
    app.add_typer(
        validation.app,
        name="validation",
        help="Manage validations (pass/fail tools) and resource attachments",
    )
    app.add_typer(
        automation_profile.app,
        name="automation-profile",
        help="Manage automation profiles that control plan execution autonomy",
    )
    app.add_typer(
        invariant.app,
        name="invariant",
        help="Manage invariant constraints for plan execution",
    )
    app.add_typer(
        _repl_app,
        name="repl",
        help="Start an interactive REPL session",
    )
    app.add_typer(
        tui.app,
        name="tui",
        help="Launch the Textual TUI",
    )
    app.add_typer(
        server_app,
        name="server",
        help="Server connection management (stub)",
    )
    app.add_typer(
        repo.app,
        name="repo",
        help="Repository indexing management",
    )

    _subcommands_registered = True


# Register subcommands eagerly on import so Typer recognizes nested commands
_register_subcommands()


def ensure_cli_commands_registered() -> None:
    """Public helper to register CLI subcommands once."""
    _register_subcommands()


# Legacy Click CLI for compatibility
cli = app


def _print_basic_help() -> None:
    """Print a lightweight help message without heavy imports."""
    typer.echo("CleverAgents - AI-powered development assistant (actor-first)")
    typer.echo("Usage: cleveragents [OPTIONS] COMMAND [ARGS]...")
    typer.echo("\nCommon commands:")
    typer.echo("  project      Project management")
    typer.echo("  actor context  Actor context management")
    typer.echo("  plan         Plan operations (actor required)")
    typer.echo("  actor        Actor management and defaults")
    typer.echo("  init         Initialize a project")
    typer.echo("  tell         Create a plan (shortcut)")
    typer.echo("  build        Build the current plan")
    typer.echo("  apply        Apply plan changes")
    typer.echo("  db           Database migration management")
    typer.echo("  auto-debug   Auto-debug operations")
    typer.echo("  repl         Interactive REPL session")
    typer.echo("  tui          Textual terminal UI")
    typer.echo("  completion   Generate shell completion script")
    typer.echo("  version      Show version")
    typer.echo("")
    typer.echo("Actors: set a default with 'agents actor set-default <name>'.")
    typer.echo("Actors only: provider/model flags were removed. Use actors instead.")
    typer.echo("Built-ins are <provider>/<model>; custom actors use local/<id>.")
    typer.echo(
        "Default or built-in actors cannot be removed; "
        "use --unsafe when the config is marked unsafe."
    )


def version_callback(value: bool) -> None:
    """Handle --version flag."""
    if value:
        # Use typer.echo for consistent output in tests (no color codes)
        typer.echo(f"CleverAgents {__version__}")
        raise typer.Exit()


def show_secrets_callback(value: bool) -> None:
    """Handle --show-secrets flag by updating the global redaction state."""
    if value:
        from cleveragents.shared.redaction import set_show_secrets

        set_show_secrets(True)


@app.callback()
def main_callback(
    ctx: typer.Context,
    version: bool = typer.Option(
        None,
        "--version",
        callback=version_callback,
        is_eager=True,
        help="Show version",
    ),
    show_secrets: bool = typer.Option(
        False,
        "--show-secrets",
        callback=show_secrets_callback,
        is_eager=True,
        help="Reveal secrets in CLI output (default: masked)",
    ),
    fmt: Annotated[
        OutputFormat,
        typer.Option(
            "--format",
            "-f",
            help=(
                "Output format for all subcommands: "
                "rich, color, table, plain, json, or yaml (default: rich)"
            ),
        ),
    ] = OutputFormat.RICH,
) -> None:
    """CleverAgents - AI-powered development assistant."""
    # Suppress debug-level logs on stdout for ALL commands so machine-readable
    # output formats (json, yaml, plain) receive clean stdout.  Commands that
    # need verbose logging can override this after parsing --log-level flags.
    from cleveragents.config.logging import configure_structlog

    configure_structlog(log_level="WARNING")
    _register_subcommands()

    # Store the selected output format in the Typer context so all subcommands
    # can read it via ctx.obj["format"] without needing their own --format flag.
    ctx.ensure_object(dict)
    ctx.obj["format"] = fmt.value


@app.command()
def version(
    ctx: typer.Context,
) -> None:
    """Display version information."""
    from cleveragents.cli.commands.system import (
        build_version_data,
        render_version_rich,
    )
    from cleveragents.cli.formatting import format_output

    ctx.ensure_object(dict)
    fmt: str = ctx.obj.get("format", OutputFormat.RICH.value)

    data = build_version_data()
    if fmt.lower() == OutputFormat.RICH.value:
        render_version_rich(data)
    else:
        typer.echo(format_output(data, fmt))


@app.command()
def info(
    ctx: typer.Context,
) -> None:
    """Display system information and configuration."""
    from cleveragents.cli.commands.system import build_info_data, render_info_rich
    from cleveragents.cli.formatting import format_output

    ctx.ensure_object(dict)
    fmt: str = ctx.obj.get("format", OutputFormat.RICH.value)

    if fmt.lower() != OutputFormat.RICH.value:
        # Suppress debug-level log output so machine-readable formats
        # (json, yaml, plain) receive clean stdout without log lines mixed in.
        from cleveragents.config.logging import configure_structlog

        configure_structlog(log_level="WARNING")

    data = build_info_data()
    if fmt.lower() == OutputFormat.RICH.value:
        render_info_rich(data)
    else:
        typer.echo(format_output(data, fmt))


@app.command()
def diagnostics(
    ctx: typer.Context,
    check: bool = typer.Option(
        False,
        "--check",
        help="Exit non-zero when any check fails",
    ),
) -> None:
    """Run system diagnostics and health checks."""
    from cleveragents.cli.commands.system import (
        build_diagnostics_data,
        render_diagnostics_rich,
    )
    from cleveragents.cli.formatting import format_output

    ctx.ensure_object(dict)
    fmt: str = ctx.obj.get("format", OutputFormat.RICH.value)

    if fmt.lower() != OutputFormat.RICH.value:
        from cleveragents.config.logging import configure_structlog

        configure_structlog(log_level="WARNING")

    data = build_diagnostics_data()
    if fmt.lower() == OutputFormat.RICH.value:
        render_diagnostics_rich(data)
    else:
        typer.echo(format_output(data, fmt))

    if check and data["has_errors"]:
        raise typer.Exit(code=1)


@app.command()
def init(
    name: Annotated[
        str | None,
        typer.Argument(help="Project name (defaults to current directory name)"),
    ] = None,
    path: Annotated[
        Path | None,
        typer.Option(
            "--path",
            "-p",
            help="Project path (defaults to current directory)",
        ),
    ] = None,
    force: Annotated[
        bool, typer.Option("--force", "-f", help="Force reinitialization")
    ] = False,
    create_ignore_file: Annotated[
        bool,
        typer.Option(
            "--create-ignore-file",
            help="Write a default .agentsignore with recommended patterns",
        ),
    ] = False,
    default_filters: Annotated[
        bool,
        typer.Option(
            "--default-filters",
            help="Seed project exclude filters with recommended defaults",
        ),
    ] = False,
    yes: Annotated[
        bool,
        typer.Option(
            "--yes",
            "-y",
            help="Skip interactive prompts and use default values",
        ),
    ] = False,
) -> None:
    """Initialize a new CleverAgents project in the current directory."""
    from cleveragents.cli.commands.project import init_command as project_init_command

    try:
        project_init_command(
            name=name,
            path=path,
            force=force,
            create_ignore_file=create_ignore_file,
            default_filters=default_filters,
            yes=yes,
        )
    except CleverAgentsError as e:
        from cleveragents.core.error_handling import classify_error

        err_console = get_err_console()
        info = classify_error(e)
        err_console.print(
            f"[red]Error [{info.code.value}] {info.code.name}:[/red] {info.message}"
        )
        if info.details:
            for key, value in info.details.items():
                err_console.print(f"  {key}: {value}")
        raise typer.Exit(1) from e
    except Exception as e:
        from cleveragents.core.error_handling import classify_error, wrap_unexpected

        err_console = get_err_console()
        safe = wrap_unexpected(e)
        info = classify_error(safe)
        err_console.print(
            f"[red]Error [{info.code.value}] {info.code.name}:[/red] {info.message}"
        )
        raise typer.Exit(1) from e


# Shortcuts for most common commands
@app.command()
def tell(
    prompt: Annotated[str, typer.Argument(help="Instructions for the AI")],
    name: Annotated[str | None, typer.Option("--name", "-n")] = None,
    actor: Annotated[
        str | None,
        typer.Option(
            "--actor",
            help=(
                "Actor to use for generation (defaults to the configured default actor)"
            ),
        ),
    ] = None,
    stream: Annotated[
        bool,
        typer.Option("--stream", help="Show real-time progress during plan generation"),
    ] = False,
) -> None:
    """Create a plan from instructions (shortcut for 'plan tell')."""
    from cleveragents.cli.commands.plan import tell as plan_tell

    kwargs: dict[str, Any] = {
        "prompt": prompt,
        "stream": stream,
    }
    if name is not None:
        kwargs["name"] = name
    if actor is not None:
        kwargs["actor"] = actor

    plan_tell(**kwargs)


@app.command()
def build(
    verbose: Annotated[
        bool, typer.Option("--verbose", "-v", help="Show detailed output")
    ] = False,
    actor: Annotated[
        str | None,
        typer.Option(
            "--actor",
            help=(
                "Actor to use for building (defaults to the configured default actor)"
            ),
        ),
    ] = None,
) -> None:
    """Build the current plan (shortcut for 'plan build')."""
    from cleveragents.cli.commands.plan import build as plan_build

    kwargs: dict[str, Any] = {"verbose": verbose}
    if actor is not None:
        kwargs["actor"] = actor

    plan_build(**kwargs)


@app.command()
def apply(
    plan_id: Annotated[
        str | None,
        typer.Argument(help="Plan ID to apply"),
    ] = None,
    yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation")] = False,
) -> None:
    """Apply plan changes (shortcut for 'plan apply')."""
    from cleveragents.cli.commands.plan import lifecycle_apply_plan

    lifecycle_apply_plan(plan_id=plan_id, yes=yes)


@app.command("context-load")
def context_load(
    paths: Annotated[list[str], typer.Argument()],
    recursive: bool = True,
) -> None:
    """Add files to context (shortcut for 'context add')."""
    from cleveragents.cli.commands.context import context_add

    context_add(paths=paths, recursive=recursive)


@app.command("context-add")
def context_add_shortcut(
    paths: Annotated[list[str], typer.Argument()],
    recursive: bool = True,
) -> None:
    """Add files to context (shortcut for 'context add')."""
    from cleveragents.cli.commands.context import context_add

    context_add(paths=paths, recursive=recursive)


@app.command()
def completion(
    shell: Annotated[
        str,
        typer.Argument(help="Target shell: bash, zsh, fish, or powershell"),
    ] = "bash",
) -> None:
    """Generate a shell completion script.

    Print a completion script for the given shell to stdout.
    Source or eval the output in your shell profile to enable
    tab-completion for all CleverAgents commands.

    Examples::

        # Bash
        agents completion bash >> ~/.bashrc

        # Zsh
        agents completion zsh >> ~/.zshrc

        # Fish
        agents completion fish > ~/.config/fish/completions/agents.fish
    """
    import subprocess

    from cleveragents.cli.constants import EXIT_USAGE
    from cleveragents.cli.errors import cli_error

    shell_lower = shell.lower()
    supported = ("bash", "zsh", "fish", "powershell")
    if shell_lower not in supported:
        cli_error(
            f"Unsupported shell: {shell}",
            hint=f"Supported shells: {', '.join(supported)}",
            exit_code=EXIT_USAGE,
        )

    env_var = f"_CLEVERAGENTS_COMPLETE={shell_lower}_source"
    result = subprocess.run(
        [sys.executable, "-m", "cleveragents"],
        capture_output=True,
        text=True,
        env={**__import__("os").environ, env_var: "1"},
    )
    if result.stdout:
        typer.echo(result.stdout, nl=False)
    else:
        typer.echo(f"# Completion script for {shell_lower}")
        typer.echo(f"# Shell: {shell_lower}")
        typer.echo("# Source this file or add it to your shell profile.")


def convert_exit_code(code: Any) -> int:
    """Convert exit code to valid range.

    Args:
        code: Exit code, None, or Click Exit exception

    Returns:
        Valid exit code (-1 for negative, 0-255 for others)
    """
    # Handle Click Exit exceptions for compatibility
    if hasattr(code, "exit_code"):
        code = code.exit_code

    if code is None:
        return 0

    # Convert to int if possible
    try:
        code = int(code)
    except (TypeError, ValueError):
        return 1

    # Allow negative codes to pass through (for test compatibility)
    if code < 0:
        return code

    # Ensure positive codes are in valid range
    return min(255, code)


def main(args: list[str] | None = None) -> int:
    """Main entry point for the CLI.

    Args:
        args: Command line arguments (defaults to sys.argv)

    Returns:
        Exit code (0 for success, non-zero for errors)
    """
    # Configure structlog at the earliest possible point so that debug-level
    # plugin_manager logs (emitted during module import) go to stderr and not
    # stdout.  This ensures machine-readable output (--format json/yaml/plain)
    # is not polluted with log lines.
    from cleveragents.config.logging import configure_structlog

    configure_structlog(log_level="WARNING")

    try:
        err_console = get_err_console()

        # Process args to handle our custom logic
        if args is None:
            # When no args provided, use sys.argv but still do our processing
            args = sys.argv[1:]

        # Remove program name if present
        if args and args[0] in ("cleveragents", "agents"):
            args = args[1:]

        if not args:
            args = ["--help"]

        # Fast paths for lightweight flags to avoid heavy imports when using the
        # real Typer app. If the app is monkeypatched (e.g., tests), fall back to
        # normal execution so patched call paths are exercised.
        if (
            len(args) == 1
            and args[0] in ("--help", "-h")
            and isinstance(app, typer.Typer)
        ):
            _print_basic_help()
            return 0
        if (
            len(args) == 1
            and args[0] in ("--version", "-V")
            and isinstance(app, typer.Typer)
        ):
            typer.echo(f"CleverAgents {__version__}")
            return 0

        # Check for invalid commands before invoking the CLI
        valid_cmds = [
            "version",
            "info",
            "diagnostics",
            "init",
            "project",
            "context",
            "plan",
            "actor",
            "action",  # v3 plan lifecycle actions
            "db",  # Database migration management
            "resource",  # Resource registry management
            "skill",  # Skill management
            "lsp",  # LSP server management
            "cleanup",  # Garbage collection and cleanup
            "config",  # Configuration management
            "session",  # Session management
            "tool",  # Tool registry management
            "validation",  # Validation management
            "auto-debug",  # Auto-debug commands
            "automation-profile",  # Automation profile management
            "invariant",  # Invariant constraint management
            "repl",  # Interactive REPL
            "tui",  # Textual TUI
            "server",  # Server connection management
            "repo",  # Repository indexing management
            "tell",  # Shortcut for plan tell
            "build",  # Shortcut for plan build
            "apply",  # Shortcut for plan apply
            "context-load",  # Shortcut for context add
            "context-add",  # Shortcut
            "completion",  # Shell completion generation
            "--help",
            "-h",
            "--version",
            "-V",
        ]
        if (
            args
            and not any(args[0] == cmd for cmd in valid_cmds)
            and not args[0].startswith("-")
        ):
            err_console.print(f"Error: Invalid command '{args[0]}'")
            err_console.print("Try 'cleveragents --help' for help.")
            return 2

        # Only register heavyweight subcommands when the invoked command
        # actually needs them.  Lightweight top-level commands (version,
        # info, diagnostics, tell, build, apply, context-load, context-add,
        # init) are defined directly on `app` and do not require the full
        # subcommand tree, avoiding expensive container/service imports.
        _LIGHTWEIGHT_COMMANDS = frozenset(
            {
                "version",
                "info",
                "diagnostics",
                "init",
                "tell",
                "build",
                "apply",
                "context-load",
                "context-add",
            }
        )
        if hasattr(app, "add_typer") and not (
            args and args[0] in _LIGHTWEIGHT_COMMANDS
        ):
            _register_subcommands()

        # Now call the app with processed args
        result = app(args, standalone_mode=False)
        # When standalone_mode=False, typer returns Exit/Abort instead of raising
        if isinstance(result, typer.Exit):
            return convert_exit_code(result.exit_code)
        if isinstance(result, typer.Abort):
            return 1
        if isinstance(result, int):
            # If app returns an int directly, use it as exit code
            return convert_exit_code(result)
        return 0
    except SystemExit as e:
        # Handle SystemExit from the app
        return convert_exit_code(e.code)
    except typer.Exit as e:
        return convert_exit_code(e.exit_code)
    except typer.Abort:
        return 1
    except CleverAgentsError as e:
        from cleveragents.core.error_handling import classify_error

        err_console = get_err_console()
        info = classify_error(e)
        err_console.print(
            f"[red]Error [{info.code.value}] {info.code.name}:[/red] {info.message}"
        )
        if info.details:
            err_console.print("[dim]Details:[/dim]")
            for key, value in info.details.items():
                err_console.print(f"  {key}: {value}")
        return 1
    except KeyboardInterrupt:
        err_console = get_err_console()
        err_console.print("\n[yellow]Interrupted by user[/yellow]")
        return 130
    except Exception as e:
        from cleveragents.core.error_handling import classify_error, wrap_unexpected

        err_console = get_err_console()
        safe = wrap_unexpected(e)
        info = classify_error(safe)
        err_console.print(
            f"[red]Error [{info.code.value}] {info.code.name}:[/red] {info.message}"
        )
        return 1


if __name__ == "__main__":
    sys.exit(main())
