from __future__ import annotations

import argparse
import sys
from pathlib import Path
from typing import List, Optional, Type

from .core import (
    AGENT_CHOICES,
    AgswitchError,
    AppPaths,
    DEFAULT_IMPORT_SEPARATOR,
    add_profile,
    apply_profile,
    build_profile_from_inputs,
    get_status,
    import_existing_profiles,
    import_profile,
    list_profiles,
    mask_secret,
    remove_profile,
)


GLOBAL_HELP = """\
Switch Claude and Codex API profiles on Linux.

Concepts:
  profile
    A local alias for one reusable config set. It can contain Claude settings,
    Codex settings, or both. Names like 'max', 'taiji', 'packy', 'remax' are
    all treated as profile names.

  agent
    Which live config you want to operate on:
      claude  only Claude Code
      codex   only Codex
      all     both Claude and Codex

Important paths:
  profile store:
    ~/.config/agentkeyswitch/profiles.json

  live files updated by 'use':
    ~/.claude/settings.json
    ~/.codex/auth.json
    ~/.codex/config.toml

Quick start:
  agswitch import-existing
  agswitch list
  agswitch use claude max
  agswitch use all taiji
  agswitch status

More help:
  agswitch help add
  agswitch help import-existing
  agswitch help use
"""


ADD_HELP = """\
Create or update a stored profile.

Two modes:
  1. Interactive
     agswitch add max

  2. Non-interactive
     agswitch add taiji --agent all \\
       --claude-token 'xxx' \\
       --claude-base-url 'https://taijiai.online/v1' \\
       --claude-model 'opus' \\
       --codex-key 'xxx' \\
       --codex-base-url 'https://taijiai.online/v1' \\
       --codex-provider 'taiji'

Notes:
  --agent all
    Create both Claude and Codex sections in one profile.

  --overwrite
    Replace an existing claude/codex section inside the same profile name.
    Without this flag, existing sections are protected.

  profile
    Local alias only. It does not need to equal the real provider name.
"""


IMPORT_HELP = """\
Import the current live config or a specified file/directory into one profile.

Examples:
  agswitch import current --agent all
  agswitch import max --agent claude
  agswitch import packy --agent codex --from ~/.codex
  agswitch import taiji --agent claude --from ~/.claude/settings.json

How --from works:
  If omitted:
    claude -> ~/.claude/settings.json
    codex  -> ~/.codex/auth.json + ~/.codex/config.toml

  If a directory is passed:
    claude -> <dir>/settings.json or <dir>/.claude/settings.json
    codex  -> <dir>/auth.json + <dir>/config.toml
            or <dir>/.codex/auth.json + <dir>/.codex/config.toml

  If a file is passed:
    claude -> that settings.json file
    codex  -> auth.json or config.toml and its sibling file
"""


IMPORT_EXISTING_HELP = """\
Batch import your existing named profile files.

Default file patterns:
  ~/.claude/settings - <profile>.json
  ~/.codex/auth - <profile>.json

How names are derived:
  ~/.claude/settings - max.json   -> profile 'max'
  ~/.codex/auth - taiji.json      -> profile 'taiji'

Separator:
  By default the separator is " - ".
  You can override it with --separator.

  Example:
    agswitch import-existing --separator "_"

  Then the tool will look for:
    ~/.claude/settings_<profile>.json
    ~/.codex/auth_<profile>.json

Merge behavior:
  If both Claude and Codex contain the same name, they are merged into one
  profile with both sections.

Examples:
  agswitch import-existing
  agswitch import-existing --include-current
  agswitch import-existing --separator "_"
  agswitch import-existing --overwrite

Useful for your current machine:
  It will pick up files like:
    settings - 88.json
    settings - max.json
    settings - remax.json
    settings - re_sp.json
    settings - re_ant.json
    settings - taiji.json
    auth - 88.json
    auth - codex.json
    auth - packy.json
    auth - taiji.json
"""


USE_HELP = """\
Apply a stored profile to live config files.

Examples:
  agswitch use claude max
  agswitch use codex packy
  agswitch use all taiji
  agswitch use all taiji --dry-run

What gets changed:
  claude
    ~/.claude/settings.json

  codex
    ~/.codex/auth.json
    ~/.codex/config.toml

Safety:
  Before writing, timestamped backups are created under:
    ~/.config/agentkeyswitch/backups/
"""


STATUS_HELP = """\
Show the currently active live config and try to match it to a stored profile.

Examples:
  agswitch status
  agswitch status --agent claude
  agswitch status --agent codex

If no stored profile matches the live files, profile will show as 'untracked'.
"""


LIST_HELP = """\
List all stored profiles from the local profile store.

Output:
  profile_name claude=mask codex=mask

Examples:
  agswitch list
"""


REMOVE_HELP = """\
Delete one stored profile from the local store.

Example:
  agswitch remove max

This does not change the current live Claude/Codex files.
"""


def formatter_class() -> Type[argparse.HelpFormatter]:
    return argparse.RawTextHelpFormatter


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="agswitch",
        description=GLOBAL_HELP,
        formatter_class=formatter_class(),
    )
    subparsers = parser.add_subparsers(dest="command", required=True)

    list_cmd = subparsers.add_parser(
        "list",
        help="List stored profiles",
        description=LIST_HELP,
        formatter_class=formatter_class(),
    )

    status = subparsers.add_parser(
        "status",
        help="Show current live agent config status",
        description=STATUS_HELP,
        formatter_class=formatter_class(),
    )
    status.add_argument(
        "--agent",
        choices=AGENT_CHOICES,
        default="all",
        help="Which live config to inspect: claude, codex, or all (default: all)",
    )

    add = subparsers.add_parser(
        "add",
        help="Create a profile",
        description=ADD_HELP,
        formatter_class=formatter_class(),
    )
    add.add_argument("profile", help="Profile name")
    add.add_argument(
        "--agent",
        choices=AGENT_CHOICES,
        default="all",
        help="Which section(s) to create inside the profile (default: all)",
    )
    add.add_argument(
        "--overwrite",
        action="store_true",
        help="Replace an existing claude/codex section under the same profile name",
    )
    add.add_argument("--claude-token", help="Claude ANTHROPIC_AUTH_TOKEN")
    add.add_argument("--claude-base-url", help="Claude ANTHROPIC_BASE_URL")
    add.add_argument("--claude-model", help="Claude model value written to settings.json")
    add.add_argument("--codex-key", help="Codex OPENAI_API_KEY")
    add.add_argument("--codex-base-url", help="Codex provider base_url")
    add.add_argument(
        "--codex-provider",
        help="Codex provider name written into config.toml, such as 88code or taiji",
    )
    add.add_argument(
        "--codex-wire-api",
        default="responses",
        help="Codex provider wire_api value (default: responses)",
    )

    import_cmd = subparsers.add_parser(
        "import",
        help="Import a live config into a profile",
        description=IMPORT_HELP,
        formatter_class=formatter_class(),
    )
    import_cmd.add_argument("profile", help="Profile name")
    import_cmd.add_argument(
        "--agent",
        choices=AGENT_CHOICES,
        default="all",
        help="Which agent config to import (default: all)",
    )
    import_cmd.add_argument(
        "--from",
        dest="source",
        help="Optional file or directory to import from; default is your live home config",
    )
    import_cmd.add_argument(
        "--overwrite",
        action="store_true",
        help="Replace an existing claude/codex section under the same profile name",
    )

    import_existing = subparsers.add_parser(
        "import-existing",
        help="Import named profile files from ~/.claude and ~/.codex",
        description=IMPORT_EXISTING_HELP,
        formatter_class=formatter_class(),
    )
    import_existing.add_argument(
        "--overwrite",
        action="store_true",
        help="Replace an existing claude/codex section under the same profile name",
    )
    import_existing.add_argument(
        "--include-current",
        action="store_true",
        help="Also import the current live config as profile 'current'",
    )
    import_existing.add_argument(
        "--separator",
        default=DEFAULT_IMPORT_SEPARATOR,
        help=(
            "Filename separator between the fixed stem and profile name "
            '(default: " - "). Example: "_" -> settings_<name>.json'
        ),
    )

    use = subparsers.add_parser(
        "use",
        help="Apply a stored profile to live config files",
        description=USE_HELP,
        formatter_class=formatter_class(),
    )
    use.add_argument("agent", choices=AGENT_CHOICES, help="Agent to switch")
    use.add_argument("profile", help="Stored profile name")
    use.add_argument(
        "--dry-run",
        action="store_true",
        help="Validate and show that the switch is possible without writing files",
    )

    remove = subparsers.add_parser(
        "remove",
        help="Delete a stored profile",
        description=REMOVE_HELP,
        formatter_class=formatter_class(),
    )
    remove.add_argument("profile", help="Stored profile name")

    help_cmd = subparsers.add_parser(
        "help",
        help="Show detailed help for the whole CLI or one command",
        description="Show detailed help.\n\nExamples:\n  agswitch help\n  agswitch help add\n  agswitch help use",
        formatter_class=formatter_class(),
    )
    help_cmd.add_argument(
        "topic",
        nargs="?",
        choices=("list", "status", "add", "import", "import-existing", "use", "remove"),
        help="Optional command name",
    )

    parser._command_parsers = {
        "list": list_cmd,
        "status": status,
        "add": add,
        "import": import_cmd,
        "import-existing": import_existing,
        "use": use,
        "remove": remove,
        "help": help_cmd,
    }
    return parser


def print_heading(title: str) -> None:
    print(title)
    print("-" * len(title))


def print_field(label: str, value: str) -> None:
    print("  {label:<12} {value}".format(label=label, value=value))


def print_block(name: str, rows: List[tuple]) -> None:
    print("- {name}".format(name=name))
    for label, value in rows:
        print_field(label, value)


def cmd_list(app_paths: AppPaths) -> int:
    profiles = list_profiles(app_paths)
    if not profiles:
        print("No stored profiles.")
        return 0
    print_heading("Stored Profiles ({count})".format(count=len(profiles)))
    for profile in profiles:
        claude_value = "yes (token: {token})".format(
            token=mask_secret(profile.claude.auth_token)
        ) if profile.claude else "no"
        codex_value = "yes (key: {key})".format(
            key=mask_secret(profile.codex.api_key)
        ) if profile.codex else "no"
        print_block(
            profile.name,
            [
                ("claude", claude_value),
                ("codex", codex_value),
            ],
        )
    return 0


def cmd_status(app_paths: AppPaths, agent: str) -> int:
    statuses = get_status(app_paths, agent)
    print_heading("Live Status")
    for item in statuses:
        rows = [("profile", item.profile_name or "untracked")]
        for key, value in item.details.items():
            rows.append((key, str(value)))
        rows.append(("paths", str(len(item.paths))))
        print_block(item.agent, rows)
        for path in item.paths:
            print("  {label:<12} {value}".format(label="", value=path))
    return 0


def cmd_add(args: argparse.Namespace, app_paths: AppPaths) -> int:
    profile = build_profile_from_inputs(
        args.profile,
        args.agent,
        claude_token=args.claude_token,
        claude_base_url=args.claude_base_url,
        claude_model=args.claude_model,
        codex_key=args.codex_key,
        codex_base_url=args.codex_base_url,
        codex_provider=args.codex_provider,
        codex_wire_api=args.codex_wire_api,
    )
    stored = add_profile(app_paths, profile, overwrite=args.overwrite)
    print(f"Saved profile '{stored.name}'.")
    return 0


def cmd_import(args: argparse.Namespace, app_paths: AppPaths) -> int:
    source = Path(args.source).expanduser() if args.source else None
    stored = import_profile(
        app_paths,
        args.profile,
        args.agent,
        source=source,
        overwrite=args.overwrite,
    )
    print(f"Imported profile '{stored.name}'.")
    return 0


def cmd_import_existing(args: argparse.Namespace, app_paths: AppPaths) -> int:
    imported = import_existing_profiles(
        app_paths,
        overwrite=args.overwrite,
        include_current=args.include_current,
        separator=args.separator,
    )
    print_heading("Imported Profiles ({count})".format(count=len(imported)))
    for profile in imported:
        agents = []
        if profile.claude:
            agents.append("claude")
        if profile.codex:
            agents.append("codex")
        print_block(
            profile.name,
            [("agents", ", ".join(agents) if agents else "empty")],
        )
    return 0


def cmd_use(args: argparse.Namespace, app_paths: AppPaths) -> int:
    backups = apply_profile(
        app_paths,
        args.agent,
        args.profile,
        dry_run=args.dry_run,
    )
    title = "Dry Run OK" if args.dry_run else "Applied Profile"
    print_heading(title)
    print_field("profile", args.profile)
    print_field("agent", args.agent)
    for agent_name, paths in backups.items():
        if not paths:
            continue
        print_block(agent_name, [("backups", str(len(paths)))])
        for path in paths:
            print("  {label:<12} {value}".format(label="", value=path))
    return 0


def cmd_remove(args: argparse.Namespace, app_paths: AppPaths) -> int:
    remove_profile(app_paths, args.profile)
    print(f"Removed profile '{args.profile}'.")
    return 0


def cmd_help(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
    if args.topic:
        parser._command_parsers[args.topic].print_help()
    else:
        parser.print_help()
    return 0


def main(argv: Optional[List[str]] = None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)
    app_paths = AppPaths.discover()

    try:
        if args.command == "list":
            return cmd_list(app_paths)
        if args.command == "status":
            return cmd_status(app_paths, args.agent)
        if args.command == "add":
            return cmd_add(args, app_paths)
        if args.command == "import":
            return cmd_import(args, app_paths)
        if args.command == "import-existing":
            return cmd_import_existing(args, app_paths)
        if args.command == "use":
            return cmd_use(args, app_paths)
        if args.command == "remove":
            return cmd_remove(args, app_paths)
        if args.command == "help":
            return cmd_help(args, parser)
        parser.error(f"Unhandled command: {args.command}")
    except AgswitchError as exc:
        print(f"Error: {exc}", file=sys.stderr)
        return 2
    except KeyboardInterrupt:
        print("Interrupted.", file=sys.stderr)
        return 130
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
