"""Harnyard CLI -- unified launcher for AI coding agent harnesses."""

from __future__ import annotations

import os
import shutil
import subprocess
import sys
from pathlib import Path

import click

from . import __version__
from .profile import find_profile, load_all_profiles
from .setup import setup_harness


EXAMPLES = """\b
Examples:
  hy                          Start default harness (from $HARNYARD_HARNESS)
  hy claude                   Start Claude Code
  hy kiro                     Start Kiro CLI (auto-creates symlinks)
  hy -p "explain this code"   Run prompt with default harness
  hy setup --all              Setup symlinks for all non-native harnesses
  hy status                   Show installed harnesses and config state
  hy info kiro                Show how Kiro maps to .claude/ config
  hy install kiro             Install Kiro CLI"""


def _harness_names() -> list[str]:
    """Get all harness names for shell completion."""
    names = []
    for p in load_all_profiles():
        names.append(p.binary)
        names.extend(p.aliases)
    return names


def _get_default_harness() -> str | None:
    """Get default harness from environment."""
    return os.environ.get("HARNYARD_HARNESS")


def _detect_busybox_harness() -> str | None:
    """Detect harness from how we were invoked (BusyBox pattern).

    If hy is symlinked as 'copilot' or 'kiro', detect that name
    and use it as the harness.
    """
    invoked_as = Path(sys.argv[0]).stem
    if invoked_as in ("hy", "harnyard"):
        return None
    # Check if the invoked name matches a harness
    profile = find_profile(invoked_as)
    if profile:
        return invoked_as
    return None


def _launch_harness(harness_name: str, extra_args: tuple[str, ...] = ()) -> None:
    """Launch a harness, handling install and setup."""
    profile = find_profile(harness_name)
    if not profile:
        click.echo(click.style(f"Unknown harness: {harness_name}", fg="red"))
        click.echo("Run 'hy list' to see available harnesses.")
        sys.exit(1)

    binary_path = shutil.which(profile.binary)
    if not binary_path:
        # Offer to install
        if profile.install_command:
            click.echo(
                click.style(f"{profile.binary} not found.", fg="yellow")
                + " Install with:"
            )
            click.echo(f"  {profile.install_command}")
            if profile.install_url:
                click.echo(f"  Docs: {profile.install_url}")
            click.echo()
            if click.confirm("Install now?"):
                click.echo(click.style(f"Running: {profile.install_command}", fg="blue"))
                result = subprocess.run(profile.install_command, shell=True)
                if result.returncode != 0:
                    click.echo(click.style("Installation failed.", fg="red"))
                    sys.exit(1)
                binary_path = shutil.which(profile.binary)
                if not binary_path:
                    click.echo(click.style(f"{profile.binary} still not found in PATH.", fg="red"))
                    sys.exit(1)
            else:
                sys.exit(0)
        else:
            click.echo(click.style(f"{profile.binary} not found in PATH", fg="red"))
            if profile.install_url:
                click.echo(f"Install: {profile.install_url}")
            sys.exit(1)

    # Auto-setup if needed
    claude_dir = Path.cwd() / ".claude"
    config_dir = Path.cwd() / profile.config_dir
    if not profile.native and not config_dir.is_dir() and claude_dir.is_dir():
        click.echo(
            click.style(f"Auto-setting up {profile.config_dir}/ from .claude/", fg="yellow")
        )
        setup_harness(profile)
        click.echo()

    # Launch -- replace process
    os.execvp(profile.binary, [profile.binary, *extra_args])


class HarnessChoice(click.ParamType):
    """Click parameter type with shell completion for harness names."""

    name = "harness"

    def shell_complete(self, ctx, param, incomplete):
        return [
            click.shell_completion.CompletionItem(name)
            for name in _harness_names()
            if name.startswith(incomplete)
        ]


HARNESS_TYPE = HarnessChoice()


# --- Subcommands (management) ---

@click.group(invoke_without_command=True, epilog=EXAMPLES)
@click.version_option(version=__version__)
@click.pass_context
def cli(ctx: click.Context) -> None:
    """Harnyard -- unified launcher for AI coding agent harnesses.

    Start Claude Code, Copilot CLI, Kiro CLI, or Pi from a single command
    with shared config under .claude/.

    Run 'hy <harness>' to start a harness directly, or use subcommands
    for setup and management.
    """
    if ctx.invoked_subcommand is None:
        # BusyBox: detect if called as a harness name
        busybox = _detect_busybox_harness()
        if busybox:
            _launch_harness(busybox, tuple(sys.argv[1:]))
            return

        # Default harness from env
        default = _get_default_harness()
        if default:
            _launch_harness(default)
            return

        # No subcommand, no default -- show help
        click.echo(ctx.get_help())


@cli.command()
@click.argument("harness", type=HARNESS_TYPE)
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
def start(harness: str, args: tuple[str, ...]) -> None:
    """Start a harness in the current project.

    All extra arguments are passed through to the harness binary.
    Auto-creates symlinks for non-native harnesses if .claude/ exists.
    If the harness isn't installed, offers to install it.
    """
    _launch_harness(harness, args)


@cli.command()
@click.argument("harness", type=HARNESS_TYPE)
def install(harness: str) -> None:
    """Install a harness."""
    profile = find_profile(harness)
    if not profile:
        click.echo(click.style(f"Unknown harness: {harness}", fg="red"))
        sys.exit(1)

    binary_path = shutil.which(profile.binary)
    if binary_path:
        click.echo(
            click.style(f"{profile.name} is already installed", fg="green")
            + f" ({binary_path})"
        )
        return

    if not profile.install_command:
        click.echo(click.style(f"No install command defined for {profile.name}", fg="red"))
        if profile.install_url:
            click.echo(f"See: {profile.install_url}")
        sys.exit(1)

    click.echo(f"Installing {click.style(profile.name, bold=True)}...")
    click.echo(click.style(f"  {profile.install_command}", fg="blue"))
    result = subprocess.run(profile.install_command, shell=True)
    if result.returncode == 0:
        click.echo(click.style("Done", fg="green"))
    else:
        click.echo(click.style("Installation failed.", fg="red"))
        sys.exit(1)


@cli.command()
@click.argument("harness", required=False, type=HARNESS_TYPE)
@click.option("--all", "setup_all", is_flag=True, help="Setup all non-native harnesses")
def setup(harness: str | None, setup_all: bool) -> None:
    """Create symlinks for a harness from .claude/.

    Links harness-specific config directories back to the canonical
    .claude/ directory so each tool finds what it needs.
    """
    if setup_all:
        for profile in load_all_profiles():
            setup_harness(profile)
            click.echo()
    elif harness:
        profile = find_profile(harness)
        if not profile:
            click.echo(click.style(f"Unknown harness: {harness}", fg="red"))
            sys.exit(1)
        setup_harness(profile)
    else:
        click.echo("Usage: hy setup <harness|--all>")
        sys.exit(1)


@cli.command()
def status() -> None:
    """Show config mapping for all harnesses in current project."""
    click.echo(click.style("Harnyard Status", bold=True))
    click.echo(f"Project: {Path.cwd()}")

    default = _get_default_harness()
    if default:
        click.echo(f"Default: {click.style(default, fg='cyan')} (from $HARNYARD_HARNESS)")

    click.echo()

    claude_dir = Path.cwd() / ".claude"

    for profile in load_all_profiles():
        binary_path = shutil.which(profile.binary)
        installed = (
            click.style("installed", fg="green")
            if binary_path
            else click.style("not found", fg="red")
        )

        if profile.native:
            if claude_dir.is_dir():
                config_status = click.style("native (.claude/)", fg="green")
            else:
                config_status = click.style("no .claude/ dir", fg="yellow")
        else:
            config_dir = Path.cwd() / profile.config_dir
            if config_dir.is_dir():
                config_status = click.style(f"configured ({profile.config_dir}/)", fg="green")
            else:
                config_status = click.style("needs setup", fg="yellow")

        click.echo(f"  {click.style(profile.name, bold=True)} ({profile.binary})")
        click.echo(f"    Binary: {installed}")
        click.echo(f"    Config: {config_status}")
        click.echo()


@cli.command(name="list")
def list_harnesses() -> None:
    """List available harnesses."""
    click.echo(click.style("Available Harnesses", bold=True))
    click.echo()

    for profile in load_all_profiles():
        binary_path = shutil.which(profile.binary)
        check = click.style(" \u2713", fg="green") if binary_path else click.style(" \u2717", fg="red")
        native_tag = click.style(" (native)", fg="green") if profile.native else ""

        click.echo(f"  {click.style(profile.binary, bold=True)}{check}{native_tag} -- {profile.name}")


@cli.command()
@click.argument("harness", type=HARNESS_TYPE)
def info(harness: str) -> None:
    """Show detailed info about a harness profile."""
    profile = find_profile(harness)
    if not profile:
        click.echo(click.style(f"Unknown harness: {harness}", fg="red"))
        sys.exit(1)

    binary_path = shutil.which(profile.binary)

    click.echo(click.style(profile.name, bold=True))
    click.echo(f"  Binary:     {profile.binary}" + (f" ({binary_path})" if binary_path else click.style(" (not found)", fg="red")))
    click.echo(f"  Config dir: {profile.config_dir}")
    click.echo(f"  Native:     {'yes' if profile.native else 'no'}")
    click.echo(f"  Aliases:    {', '.join(profile.aliases) if profile.aliases else 'none'}")

    if profile.context_target:
        click.echo(f"  Context:    .claude/{profile.context_source} -> {profile.config_dir}/{profile.context_target}")
    else:
        click.echo(f"  Context:    .claude/{profile.context_source}")

    if profile.skills_dir:
        click.echo(f"  Skills:     {profile.config_dir}/{profile.skills_dir}/ ({profile.skills_format})")

    if profile.install_command:
        click.echo(f"  Install:    {profile.install_command}")

    if profile.install_url:
        click.echo(f"  Docs:       {profile.install_url}")

    if profile.symlinks:
        click.echo("  Symlinks:")
        for sl in profile.symlinks:
            click.echo(f"    {sl.source} -> {sl.target}")


# --- Direct harness invocation ---
# Allow 'hy claude ...' without the 'start' subcommand.
# Click doesn't natively support this, so we intercept in main().

def main() -> None:
    """Entry point that supports direct harness invocation.

    Allows:
      hy claude --version     (direct, no 'start' subcommand)
      hy start claude --version  (explicit)
      hy status               (subcommand)
      copilot --version       (BusyBox -- hy symlinked as copilot)
    """
    # BusyBox detection: if called as a harness name, launch directly
    invoked_as = Path(sys.argv[0]).stem
    if invoked_as not in ("hy", "harnyard", "__main__"):
        profile = find_profile(invoked_as)
        if profile:
            _launch_harness(invoked_as, tuple(sys.argv[1:]))
            return

    # Check if first arg is a harness name (not a subcommand)
    if len(sys.argv) > 1:
        first_arg = sys.argv[1]
        # Don't intercept flags or known subcommands
        known_subcommands = {"start", "install", "setup", "status", "list", "info", "--help", "-h", "--version"}
        if first_arg not in known_subcommands and not first_arg.startswith("-"):
            profile = find_profile(first_arg)
            if profile:
                _launch_harness(first_arg, tuple(sys.argv[2:]))
                return

    # Fall through to Click CLI
    cli()
