#!/usr/bin/env python3
from __future__ import annotations

"""Toggle ``ANTHROPIC_BASE_URL`` in ``~/.claude/settings.json``.

Three actions:

- ``bifrost``: write ``http://<host>:<port>/anthropic`` into the file's
  ``env.ANTHROPIC_BASE_URL``. If the key was already set, save the prior value
  to ``~/.config/bifrost/claude-url-backup.txt`` so ``direct`` can restore it.
- ``direct``: restore the prior value (or remove the key if there is no
  backup) and clear the backup file.
- ``status``: print the current URL state and whether a backup exists.

The script is idempotent: rerunning the same action with the file already in
the matching state is a no-op.

Notes for callers:

- Claude Code reads ``settings.json`` once per session. Editing the file does
  not reconfigure a running Claude session; restart Claude Code after
  toggling.
- The script never overwrites a URL it did not set itself. If the file's
  ``ANTHROPIC_BASE_URL`` does not point at the expected Bifrost host:port,
  ``direct`` leaves it alone rather than guessing the user's true default.
"""

import argparse
import json
import os
import sys
from pathlib import Path

DEFAULT_SETTINGS: Path = Path.home() / ".claude" / "settings.json"
BACKUP_PATH: Path = Path.home() / ".config" / "bifrost" / "claude-url-backup.txt"
BIFROST_HOST: str = os.environ.get("BIFROST_HOST", "127.0.0.1")
BIFROST_PORT: str = os.environ.get("BIFROST_PORT", "8471")
BIFROST_URL: str = f"http://{BIFROST_HOST}:{BIFROST_PORT}/anthropic"


def _read_settings(path: Path) -> dict:
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        print(
            f"error: {path} is not valid JSON: {exc}",
            file=sys.stderr,
        )
        sys.exit(2)


def _write_settings(path: Path, settings: dict) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")


def _current_url(settings: dict) -> str | None:
    env = settings.get("env")
    if not isinstance(env, dict):
        return None
    value = env.get("ANTHROPIC_BASE_URL")
    return value if isinstance(value, str) else None


def _save_backup(url: str, dry_run: bool) -> None:
    if BACKUP_PATH.exists() and BACKUP_PATH.read_text(encoding="utf-8").strip() == url:
        return
    prefix = "[dry-run] would save" if dry_run else "saved prior"
    print(f"{prefix} ANTHROPIC_BASE_URL={url!r} to {BACKUP_PATH}")
    if dry_run:
        return
    BACKUP_PATH.parent.mkdir(parents=True, exist_ok=True)
    BACKUP_PATH.write_text(url + "\n", encoding="utf-8")


def _action_bifrost(path: Path, dry_run: bool) -> int:
    settings = _read_settings(path)
    existing = _current_url(settings)
    if existing == BIFROST_URL:
        print(f"ANTHROPIC_BASE_URL already {BIFROST_URL}; no-op")
        return 0
    if existing is not None:
        _save_backup(existing, dry_run)
    env = settings.setdefault("env", {})
    if not isinstance(env, dict):
        print(
            f"error: {path} has a non-object 'env' key",
            file=sys.stderr,
        )
        return 2
    env["ANTHROPIC_BASE_URL"] = BIFROST_URL
    if dry_run:
        print(f"[dry-run] would write ANTHROPIC_BASE_URL={BIFROST_URL} to {path}")
        return 0
    _write_settings(path, settings)
    print(f"wrote ANTHROPIC_BASE_URL={BIFROST_URL} to {path}")
    print("note: restart any running Claude Code session for this to take effect")
    return 0


def _action_direct(path: Path, dry_run: bool) -> int:
    settings = _read_settings(path)
    existing = _current_url(settings)
    if existing is None:
        print("ANTHROPIC_BASE_URL is unset; no-op")
        return 0
    if existing != BIFROST_URL:
        print(
            f"ANTHROPIC_BASE_URL={existing!r} is not Bifrost-managed; leaving it alone"
        )
        return 0
    backup: str | None = None
    if BACKUP_PATH.exists():
        candidate = BACKUP_PATH.read_text(encoding="utf-8").strip()
        if candidate:
            backup = candidate
    env = settings.setdefault("env", {})
    if not isinstance(env, dict):
        print(
            f"error: {path} has a non-object 'env' key",
            file=sys.stderr,
        )
        return 2
    if backup is not None:
        env["ANTHROPIC_BASE_URL"] = backup
        if dry_run:
            print(f"[dry-run] would restore ANTHROPIC_BASE_URL={backup!r} in {path}")
            return 0
        _write_settings(path, settings)
        BACKUP_PATH.unlink(missing_ok=True)
        print(f"restored ANTHROPIC_BASE_URL={backup!r} in {path}")
    else:
        env.pop("ANTHROPIC_BASE_URL", None)
        if not env:
            settings.pop("env", None)
        if dry_run:
            print(f"[dry-run] would remove ANTHROPIC_BASE_URL from {path}")
            return 0
        _write_settings(path, settings)
        print(f"removed ANTHROPIC_BASE_URL from {path}")
    print("note: restart any running Claude Code session for this to take effect")
    return 0


def _action_status(path: Path) -> int:
    settings = _read_settings(path)
    url = _current_url(settings)
    if url is None:
        print("ANTHROPIC_BASE_URL: <unset>")
    elif url == BIFROST_URL:
        print(f"ANTHROPIC_BASE_URL: Bifrost ({url})")
    else:
        print(f"ANTHROPIC_BASE_URL: custom ({url})")
    print(f"backup at {BACKUP_PATH}: {'yes' if BACKUP_PATH.exists() else 'no'}")
    return 0


def _build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Toggle ANTHROPIC_BASE_URL in ~/.claude/settings.json.",
    )
    parser.add_argument(
        "action",
        choices=("bifrost", "direct", "status"),
        help="What to do. 'bifrost' points Claude at the local gateway; "
        "'direct' restores the prior value; 'status' reports the current state.",
    )
    parser.add_argument(
        "--settings",
        type=Path,
        default=DEFAULT_SETTINGS,
        help=f"Path to settings.json (default: {DEFAULT_SETTINGS})",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Print intended changes without modifying any file.",
    )
    return parser


def main(argv: list[str] | None = None) -> int:
    args = _build_parser().parse_args(argv)
    if args.action == "bifrost":
        return _action_bifrost(args.settings, args.dry_run)
    if args.action == "direct":
        return _action_direct(args.settings, args.dry_run)
    return _action_status(args.settings)


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