#!/usr/bin/env python3
"""CLI for the packetdial skill — NetSapiens SNAPsolution API v2.

Read subcommands run freely. Write subcommands (create-*, update-*, delete-*,
raw with a non-GET method) refuse to run unless --confirm is passed; without it
they print what they WOULD do and exit non-zero.

Output: --json emits raw JSON (default for most commands); a few commands render
a readable table when --json is omitted.

Read examples:
  python ns.py status
  python ns.py whoami
  python ns.py domains
  python ns.py domain <domain>
  python ns.py users <domain>
  python ns.py user <domain> <user>
  python ns.py phones <domain>
  python ns.py dids <domain>
  python ns.py devices <domain> <user>
  python ns.py cdrs --domain <domain> --start 2026-06-01 --end 2026-06-02
  python ns.py resellers

Write examples (all require --confirm):
  python ns.py create-domain --body '{"domain":"acme","description":"Acme Inc"}' --confirm
  python ns.py create-user <domain> --body '{"user":"101","name-first-name":"Jane"}' --confirm
  python ns.py delete-user <domain> <user> --confirm

Escape hatch (raw request against any of the 239 v2 paths):
  python ns.py raw GET domains/acme/users
  python ns.py raw POST domains/acme/users --body '{...}' --confirm
"""
from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys

from ns_client import NetSapiensClient, PacketDialError


def _log_skill_error(skill, msg, context=""):
    """Soft-fail: append a functional-error entry to errorlog.md (never throws)."""
    try:
        root = os.environ.get("CLAUDETOOLS_ROOT") or os.path.abspath(
            os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")
        )
        h = os.path.join(root, ".claude", "scripts", "log-skill-error.sh")
        if not os.path.exists(h):
            return
        a = ["bash", h, skill, msg]
        if context:
            a += ["--context", context]
        subprocess.run(a, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                       timeout=10)
    except Exception:
        pass


def _emit(obj) -> None:
    print(json.dumps(obj, indent=2, ensure_ascii=False, default=str))


def _parse_body(raw: str | None) -> dict | None:
    if raw is None:
        return None
    try:
        parsed = json.loads(raw)
    except json.JSONDecodeError as exc:
        raise SystemExit(f"--body is not valid JSON: {exc}")
    if not isinstance(parsed, dict):
        raise SystemExit("--body must be a JSON object")
    return parsed


def _require_confirm(args, action: str, detail: str) -> None:
    if not getattr(args, "confirm", False):
        print(f"[DRY RUN] Would {action}: {detail}")
        print("Refusing to perform a write without --confirm. Re-run with --confirm.")
        raise SystemExit(2)


def main(argv=None) -> int:
    p = argparse.ArgumentParser(prog="ns.py", description="PacketDial / NetSapiens v2 CLI")
    p.add_argument("--json", action="store_true", help="emit raw JSON (default for most)")
    sub = p.add_subparsers(dest="cmd", required=True)

    # --- read ---
    sub.add_parser("status", help="API version + authenticated key identity")
    sub.add_parser("whoami", help="details of the authenticated API key")
    sub.add_parser("domains", help="list all domains")
    sp = sub.add_parser("domain", help="one domain"); sp.add_argument("domain")
    sp = sub.add_parser("users", help="users in a domain"); sp.add_argument("domain")
    sp = sub.add_parser("user", help="one user"); sp.add_argument("domain"); sp.add_argument("user")
    sp = sub.add_parser("phones", help="phones (devices) in a domain"); sp.add_argument("domain")
    sp = sub.add_parser("dids", help="phone numbers in a domain"); sp.add_argument("domain")
    sp = sub.add_parser("devices", help="devices for a user"); sp.add_argument("domain"); sp.add_argument("user")
    sub.add_parser("resellers", help="list resellers")
    sp = sub.add_parser("cdrs", help="call detail records")
    sp.add_argument("--domain"); sp.add_argument("--start"); sp.add_argument("--end")
    sp.add_argument("--limit", type=int, default=100)

    # --- write (gated) ---
    sp = sub.add_parser("create-domain"); sp.add_argument("--body", required=True); sp.add_argument("--confirm", action="store_true")
    sp = sub.add_parser("create-user"); sp.add_argument("domain"); sp.add_argument("--body", required=True); sp.add_argument("--confirm", action="store_true")
    sp = sub.add_parser("update-user"); sp.add_argument("domain"); sp.add_argument("user"); sp.add_argument("--body", required=True); sp.add_argument("--confirm", action="store_true")
    sp = sub.add_parser("delete-user"); sp.add_argument("domain"); sp.add_argument("user"); sp.add_argument("--confirm", action="store_true")
    sp = sub.add_parser("create-phone"); sp.add_argument("domain"); sp.add_argument("--body", required=True); sp.add_argument("--confirm", action="store_true")
    sp = sub.add_parser("create-did"); sp.add_argument("domain"); sp.add_argument("--body", required=True); sp.add_argument("--confirm", action="store_true")

    # --- raw escape hatch ---
    sp = sub.add_parser("raw", help="raw request against any v2 path")
    sp.add_argument("method", choices=["GET", "POST", "PUT", "PATCH", "DELETE"])
    sp.add_argument("path", help="relative path, e.g. domains/acme/users")
    sp.add_argument("--body"); sp.add_argument("--confirm", action="store_true")

    args = p.parse_args(argv)
    client = NetSapiensClient()

    try:
        if args.cmd == "status":
            _emit({"version": client.version(), "apiKey": client.whoami()})
        elif args.cmd == "whoami":
            _emit(client.whoami())
        elif args.cmd == "domains":
            _emit(client.domains())
        elif args.cmd == "domain":
            _emit(client.domain(args.domain))
        elif args.cmd == "users":
            _emit(client.users(args.domain))
        elif args.cmd == "user":
            _emit(client.user(args.domain, args.user))
        elif args.cmd == "phones":
            _emit(client.phones(args.domain))
        elif args.cmd == "dids":
            _emit(client.phonenumbers(args.domain))
        elif args.cmd == "devices":
            _emit(client.devices(args.domain, args.user))
        elif args.cmd == "resellers":
            _emit(client.resellers())
        elif args.cmd == "cdrs":
            filters = {"limit": args.limit}
            if args.start:
                filters["start-date"] = args.start
            if args.end:
                filters["end-date"] = args.end
            _emit(client.cdrs(domain=args.domain, **filters))

        elif args.cmd == "create-domain":
            body = _parse_body(args.body)
            _require_confirm(args, "create domain", json.dumps(body))
            _emit(client.create_domain(body))
        elif args.cmd == "create-user":
            body = _parse_body(args.body)
            _require_confirm(args, "create user", f"{args.domain}: {json.dumps(body)}")
            _emit(client.create_user(args.domain, body))
        elif args.cmd == "update-user":
            body = _parse_body(args.body)
            _require_confirm(args, "update user", f"{args.domain}/{args.user}: {json.dumps(body)}")
            _emit(client.update_user(args.domain, args.user, body))
        elif args.cmd == "delete-user":
            _require_confirm(args, "DELETE user", f"{args.domain}/{args.user}")
            _emit(client.delete_user(args.domain, args.user))
        elif args.cmd == "create-phone":
            body = _parse_body(args.body)
            _require_confirm(args, "create phone", f"{args.domain}: {json.dumps(body)}")
            _emit(client.create_phone(args.domain, body))
        elif args.cmd == "create-did":
            body = _parse_body(args.body)
            _require_confirm(args, "create phone number", f"{args.domain}: {json.dumps(body)}")
            _emit(client.create_phonenumber(args.domain, body))

        elif args.cmd == "raw":
            body = _parse_body(args.body)
            if args.method != "GET":
                _require_confirm(args, f"{args.method} {args.path}", json.dumps(body))
            _emit(client.request(args.method, args.path, json_body=body))
        else:
            p.error(f"unknown command {args.cmd}")
    except PacketDialError as exc:
        print(f"[ERROR] {exc}", file=sys.stderr)
        _log_skill_error("packetdial", f"{exc}",
                         context=f"cmd={getattr(args, 'cmd', '?')}")
        return 1
    return 0


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