"""Packaged CLI entrypoint for AIDA (AI Developer Assistant).

This module exists to support the "aida-mcp separation" use-case:
- build/install the server as a Python package (e.g. via `uv tool install`)
- point Cursor/Claude MCP config to the installed executable

The default invocation (no args) starts the MCP server in stdio mode.

IMPORTANT: When running as an MCP server, stdout must be reserved for JSON-RPC.
Do not print anything to stdout before starting the server.
"""

from __future__ import annotations

import argparse
import os
import shutil
import sys
from pathlib import Path

from aida_mcp.server import mcp_install
from aida_mcp.server.commit_cli import CommitMessageInput, commit_with_message, render_commit_message
from aida_mcp.server.repo_onboarding import doctor_repo, init_repo, migrate_repo
from aida_mcp.tools.core.git_policy_config import load_git_policy


def _maybe_setup_java_env() -> None:
    """Best-effort: add SDKMAN Java to PATH and set JAVA_HOME.

    This mirrors the legacy `start_mcp_server.sh` behavior. It is intentionally silent unless it needs to
    report a fatal issue (we currently have none).
    """

    java_home = Path.home() / ".sdkman" / "candidates" / "java" / "current"
    java_bin = java_home / "bin"
    if java_bin.is_dir():
        os.environ["PATH"] = f"{java_bin}:{os.environ.get('PATH', '')}"
        os.environ.setdefault("JAVA_HOME", str(java_home))


def _maybe_setup_codeartifact_token() -> None:
    """Best-effort: load CodeArtifact token for Gradle dependency resolution."""

    token_path = Path.home() / ".gradle" / ".codeartifact-token"
    if not token_path.is_file():
        return

    try:
        token = token_path.read_text(encoding="utf-8").strip()
    except Exception as exc:  # noqa: BLE001
        # Do not break server startup on a local token read failure.
        print(f"[aida-mcp] WARNING: Failed to read {token_path}: {exc}", file=sys.stderr)
        return

    if token:
        os.environ.setdefault("CODEARTIFACT_AUTH_TOKEN", token)


def _run_stdio_server() -> None:
    # Environment must be set up before the server imports any code that might spawn tools.
    _maybe_setup_java_env()
    _maybe_setup_codeartifact_token()

    # Delegate to the existing module entrypoint.
    from aida_mcp.server.__main__ import main as server_main

    server_main()


def _install_mcp(args: argparse.Namespace) -> None:
    command_path = mcp_install.resolve_self_command_path(sys.argv[0])
    targets = mcp_install.resolve_targets(args.target)
    if not targets:
        print("[aida-mcp][install-mcp][error] No --target provided.")
        print("[aida-mcp][install-mcp][error] This command writes GLOBAL ($HOME) MCP configs and is now gated.")
        print("[aida-mcp][install-mcp][error] Recommended: run `aida-mcp init` in the repo root.")
        print("[aida-mcp][install-mcp][error] If you really want global config, rerun with: --global --target cursor")
        raise SystemExit(2)

    for target in targets:
        if target == "cursor":
            mcp_install.install_for_cursor(
                command_path=command_path,
                workspace_root=args.workspace_root,
                config_root=args.config_root,
                allow_global=bool(args.global_config),
            )
        elif target == "claude":
            mcp_install.install_for_claude(
                command_path=command_path,
                workspace_root=args.workspace_root,
                config_root=args.config_root,
                allow_global=bool(args.global_config),
            )
        elif target == "jetbrains":
            mcp_install.install_for_jetbrains(
                command_path=command_path,
                workspace_root=args.workspace_root,
                config_root=args.config_root,
                allow_global=bool(args.global_config),
            )
        elif target == "junie":
            mcp_install.install_for_junie(
                command_path=command_path,
                workspace_root=args.workspace_root,
                config_root=args.config_root,
                allow_global=bool(args.global_config),
            )

    mcp_install.log("Installation complete. Restart your editor(s) to pick up the MCP server.")


def _init(args: argparse.Namespace) -> None:
    command_path = mcp_install.resolve_self_command_path(sys.argv[0])
    workspace_root = Path(args.workspace_root).expanduser().resolve() if args.workspace_root else Path.cwd().resolve()
    result = init_repo(
        workspace_root=workspace_root,
        command_path=command_path,
        force=bool(args.force),
        vendor_rules=bool(args.vendor_rules),
    )
    print("[aida-mcp][init] wrote:")
    for p in result.wrote_files:
        print(f"  - {p}")
    if result.skipped_files:
        print("[aida-mcp][init] skipped (already existed):")
        for p in result.skipped_files:
            print(f"  - {p}")


def _doctor(args: argparse.Namespace) -> None:
    workspace_root = Path(args.workspace_root).expanduser().resolve() if args.workspace_root else Path.cwd().resolve()
    code = doctor_repo(workspace_root=workspace_root)
    raise SystemExit(code)


def _migrate(args: argparse.Namespace) -> None:
    command_path = mcp_install.resolve_self_command_path(sys.argv[0])
    workspace_root = Path(args.workspace_root).expanduser().resolve() if args.workspace_root else Path.cwd().resolve()
    result = migrate_repo(
        workspace_root=workspace_root,
        command_path=command_path,
        client_only=bool(args.client_only),
        dry_run=bool(args.dry_run),
        force=bool(args.force),
    )
    print(f"[aida-mcp][migrate] backups_dir: {result.backups_dir}")
    if result.backed_up:
        print("[aida-mcp][migrate] backed up:")
        for p in result.backed_up:
            print(f"  - {p}")
    if result.removed:
        print("[aida-mcp][migrate] removed:")
        for p in result.removed:
            print(f"  - {p}")
    if result.wrote:
        print("[aida-mcp][migrate] wrote:")
        for p in result.wrote:
            print(f"  - {p}")


def _commit(args: argparse.Namespace) -> None:
    workspace_root = Path(args.workspace_root).expanduser().resolve() if args.workspace_root else Path.cwd().resolve()
    git_policy = load_git_policy(workspace_root)
    body_value = ""
    if args.body is not None:
        body_value = str(args.body)
    elif args.body_file:
        body_value = Path(args.body_file).read_text(encoding="utf-8")
    elif bool(args.body_stdin):
        body_value = sys.stdin.read()
    elif args.body_line:
        body_value = "\n".join([str(line) for line in args.body_line])

    ai_share_values: list[str]
    if args.ai_share_auto:
        if not git_policy.commit.enable_ai_share_auto:
            raise ValueError(
                "ai-share-auto is disabled by git policy. "
                "Set git_policy.commit.enable_ai_share_auto: true in .aida/git_policy.yaml to enable it."
            )
        ai_share_values = [f"{str(args.ai_share_auto).strip().lower()}=100"]
    else:
        ai_share_values = [str(x) for x in (args.ai_share or [])]

    message = render_commit_message(
        CommitMessageInput(
            commit_type=str(args.type),
            scope=str(args.scope) if args.scope else None,
            title=str(args.title),
            body=body_value or None,
            risk=str(args.risk),
            jira=[str(x) for x in (args.jira or [])],
            co_authored_by=[str(x) for x in (args.co_authored_by or [])],
            ai_share=ai_share_values,
        ),
        policy=git_policy.commit,
    )

    if bool(args.dry_run):
        print("[aida-mcp][commit] dry-run: would commit with message:")
        print("---")
        print(message.rstrip())
        print("---")
        return

    repo_root, staged_files = commit_with_message(
        workspace_root=workspace_root,
        message=message,
        stage_all=bool(args.all),
    )
    print(f"[aida-mcp][commit] committed in {repo_root}")
    print("[aida-mcp][commit] files:")
    for file_path in staged_files:
        print(f"  - {file_path}")


def _alias_status(args: argparse.Namespace) -> None:
    name = str(args.name or "aida")
    resolved_on_path = shutil.which(name)

    command_path = mcp_install.resolve_self_command_path(sys.argv[0])
    scripts_dir = command_path.parent
    alias_path = scripts_dir / name

    print(f"[aida-mcp][alias] name: {name}")
    print(f"[aida-mcp][alias] aida-mcp: {command_path}")
    print(f"[aida-mcp][alias] scripts_dir: {scripts_dir}")
    print(f"[aida-mcp][alias] alias_path: {alias_path}")

    if resolved_on_path:
        print(f"[aida-mcp][alias] {name} resolves on PATH: {resolved_on_path}")
    else:
        print(f"[aida-mcp][alias] {name} not found on PATH")

    if alias_path.exists():
        print("[aida-mcp][alias] alias file exists next to aida-mcp")
    else:
        print("[aida-mcp][alias] alias file NOT present next to aida-mcp")


def _alias_install(args: argparse.Namespace) -> None:
    name = str(args.name or "aida")
    force = bool(getattr(args, "force", False))

    resolved_on_path = shutil.which(name)
    if resolved_on_path and not force:
        print(f"[aida-mcp][alias][error] Refusing to install alias '{name}': command already exists on PATH.")
        print(f"[aida-mcp][alias][error] Found: {resolved_on_path}")
        print("[aida-mcp][alias][error] Keep using 'aida-mcp', or rerun with --force if you understand the risk.")
        raise SystemExit(2)

    command_path = mcp_install.resolve_self_command_path(sys.argv[0])
    scripts_dir = command_path.parent
    alias_path = scripts_dir / name

    wrapper = (
        "#!/usr/bin/env sh\n"
        "# Generated by `aida-mcp alias install`.\n"
        "# This wrapper lives next to the installed `aida-mcp` script.\n"
        "set -e\n"
        'exec "$(dirname "$0")/aida-mcp" "$@"\n'
    )

    alias_path.write_text(wrapper, encoding="utf-8")
    alias_path.chmod(0o755)
    print(f"[aida-mcp][alias] Installed alias '{name}' at {alias_path}")
    if resolved_on_path:
        print(
            f"[aida-mcp][alias] Note: '{name}' already existed on PATH ({resolved_on_path}); PATH precedence decides which runs."
        )


def _alias_uninstall(args: argparse.Namespace) -> None:
    name = str(args.name or "aida")

    command_path = mcp_install.resolve_self_command_path(sys.argv[0])
    scripts_dir = command_path.parent
    alias_path = scripts_dir / name

    if not alias_path.exists():
        print(f"[aida-mcp][alias] Alias '{name}' is not installed at {alias_path}")
        return

    try:
        current = alias_path.read_text(encoding="utf-8")
    except Exception:
        current = ""

    marker = "# Generated by `aida-mcp alias install`."
    if marker not in current:
        print(
            f"[aida-mcp][alias][error] Refusing to remove '{alias_path}': file does not look like an aida-mcp alias wrapper."
        )
        print("[aida-mcp][alias][error] Remove it manually if you are sure.")
        raise SystemExit(2)

    alias_path.unlink()
    print(f"[aida-mcp][alias] Removed alias '{name}' at {alias_path}")


def _build_arg_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog="aida-mcp", add_help=True)
    sub = parser.add_subparsers(dest="command")

    init = sub.add_parser("init", help="Initialize this repo for AIDA (create .aida skeleton and repo-local stubs).")
    init.add_argument("--workspace-root", help="Target workspace root (default: cwd).")
    init.add_argument("--force", action="store_true", help="Overwrite files where safe (stubs/config).")
    init.add_argument(
        "--vendor-rules",
        action="store_true",
        help="Enable vendor/example embedded rules by adding AIDA_ENABLE_EMBEDDED_VENDOR_RULES=true to generated mcp.json.",
    )
    init.set_defaults(_handler=_init)

    doctor = sub.add_parser("doctor", help="Check whether this repo is ready for AIDA.")
    doctor.add_argument("--workspace-root", help="Target workspace root (default: cwd).")
    doctor.set_defaults(_handler=_doctor)

    migrate = sub.add_parser("migrate", help="Migrate legacy repo/client setup to AIDA.")
    migrate.add_argument("--workspace-root", help="Target workspace root (default: cwd).")
    migrate.add_argument(
        "--client-only", action="store_true", help="Only migrate client instruction files (no config dir rename)."
    )
    migrate.add_argument("--dry-run", action="store_true", help="Report actions without writing/removing files.")
    migrate.add_argument("--force", action="store_true", help="Overwrite/replace when conflicts exist.")
    migrate.set_defaults(_handler=_migrate)

    commit = sub.add_parser(
        "commit",
        help="Create git commit with enforced footer policy (risk required, JIRA optional).",
    )
    commit.add_argument("--workspace-root", help="Target workspace root (default: cwd).")
    commit.add_argument(
        "--all",
        action="store_true",
        help="Stage all changes (`git add -A`) before committing.",
    )
    commit.add_argument("--dry-run", action="store_true", help="Print composed message without creating a commit.")
    commit.add_argument("--type", default="chore", help="Commit type prefix (default: chore).")
    commit.add_argument("--scope", help="Optional commit scope, rendered as type(scope): ...")
    commit.add_argument("--title", required=True, help="Commit title text (subject line).")
    body_group = commit.add_mutually_exclusive_group()
    body_group.add_argument("--body", help="Commit body text (single argument; may include newlines).")
    body_group.add_argument("--body-file", help="Path to file containing commit body text.")
    body_group.add_argument("--body-stdin", action="store_true", help="Read commit body from stdin.")
    body_group.add_argument(
        "--body-line",
        action="append",
        help="Commit body line (repeatable). Lines are joined with newline.",
    )
    commit.add_argument("--risk", required=True, help="Required risk footer value (see .aida/git_policy.yaml).")
    commit.add_argument(
        "--jira",
        action="append",
        help="Optional JIRA ID (repeat flag for multiple values).",
    )
    commit.add_argument(
        "--co-authored-by",
        action="append",
        help="Optional co-author value (`Name <email>`). Repeatable.",
    )
    ai_share_group = commit.add_mutually_exclusive_group()
    ai_share_group.add_argument(
        "--ai-share",
        action="append",
        help="Optional AI contribution entry as actor=percent (repeatable, total must equal 100).",
    )
    ai_share_group.add_argument(
        "--ai-share-auto",
        help="Set deterministic AI share as <actor>=100 (for example: cursor, claude). Policy-gated.",
    )
    commit.set_defaults(_handler=_commit)

    install = sub.add_parser(
        "install-mcp",
        help="Install MCP config for Cursor/Claude/JetBrains",
    )
    install.add_argument(
        "--target",
        action="append",
        choices=("cursor", "claude", "jetbrains", "junie", "all"),
        help="Target global config(s) to install. Repeatable. Use 'all' for all targets.",
    )
    install.add_argument(
        "--global",
        dest="global_config",
        action="store_true",
        help="Allow writing global ($HOME) client configs. Without this flag, install-mcp refuses to write.",
    )
    install.add_argument(
        "--workspace-root",
        help="Optional: pin `AIDA_WORKSPACE_ROOT` in the installed config (single-repo mode).",
    )
    install.add_argument(
        "--config-root",
        help="Optional: pin `AIDA_CONFIG_ROOT` in the installed config (single-repo mode).",
    )
    install.set_defaults(_handler=_install_mcp)

    alias = sub.add_parser(
        "alias",
        help="Manage optional 'aida' CLI alias (wrapper next to aida-mcp).",
    )
    alias_sub = alias.add_subparsers(dest="alias_command")

    alias_status = alias_sub.add_parser("status", help="Show alias status and PATH resolution")
    alias_status.add_argument("--name", default="aida", help="Alias command name (default: aida)")
    alias_status.set_defaults(_handler=_alias_status)

    alias_install = alias_sub.add_parser("install", help="Install alias wrapper (fails if name exists on PATH)")
    alias_install.add_argument("--name", default="aida", help="Alias command name (default: aida)")
    alias_install.add_argument(
        "--force",
        action="store_true",
        help="Install even if the alias name already exists on PATH (PATH precedence still applies).",
    )
    alias_install.set_defaults(_handler=_alias_install)

    alias_uninstall = alias_sub.add_parser("uninstall", help="Remove alias wrapper installed next to aida-mcp")
    alias_uninstall.add_argument("--name", default="aida", help="Alias command name (default: aida)")
    alias_uninstall.set_defaults(_handler=_alias_uninstall)

    return parser


def main(argv: list[str] | None = None) -> None:
    """Console script entrypoint.

    Default behavior (no args): run the MCP stdio server.

    Subcommands are intended for local developer tasks (e.g. config installation) and may
    print to stdout/stderr freely.
    """

    _argv = list(sys.argv[1:] if argv is None else argv)
    if not _argv:
        _run_stdio_server()
        return

    parser = _build_arg_parser()
    parsed = parser.parse_args(_argv)
    handler = getattr(parsed, "_handler", None)
    if handler is None:
        parser.print_help(file=sys.stderr)
        raise SystemExit(2)
    handler(parsed)
