import argparse
import json
import os
from typing import Any, Dict
import requests

CONFIG_DIR = os.path.expanduser("~/.cxv")
CONFIG_PATH = os.path.join(CONFIG_DIR, "config.json")
DEFAULT_BASE_URL = "https://api.cxvre.com"


def _load_config() -> Dict[str, Any]:
    if not os.path.exists(CONFIG_PATH):
        return {}
    with open(CONFIG_PATH, "r", encoding="utf-8") as f:
        return json.load(f)


def _save_config(data: Dict[str, Any]) -> None:
    os.makedirs(CONFIG_DIR, exist_ok=True)
    with open(CONFIG_PATH, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2)


def _session_from_config(cfg: Dict[str, Any]) -> requests.Session:
    sess = requests.Session()
    token = cfg.get("token")
    if token:
        sess.headers.update({"Authorization": f"Bearer {token}", "x-cxv-cli": token})
    cookies = cfg.get("cookies", {})
    if isinstance(cookies, dict):
        sess.cookies.update(cookies)
    return sess


def _require_session(cfg: Dict[str, Any]) -> requests.Session:
    if not cfg.get("cookies") and not cfg.get("token"):
        raise SystemExit("Not logged in. Run: cxv login --email <email> --password <password> or cxv login --token <token>")
    return _session_from_config(cfg)


def _base_url(cfg: Dict[str, Any]) -> str:
    return cfg.get("base_url", DEFAULT_BASE_URL)


def _request(sess: requests.Session, method: str, url: str, **kwargs: Any) -> Dict[str, Any]:
    res = sess.request(method, url, timeout=20, **kwargs)
    if not res.ok:
        raise SystemExit(f"Request failed: {res.status_code} {res.text}")
    if res.text:
        return res.json()
    return {}


def cmd_login(args: argparse.Namespace) -> None:
    base_url = args.base_url or DEFAULT_BASE_URL
    if args.token:
        cfg = {"base_url": base_url, "token": args.token}
        _save_config(cfg)
        print("Token saved")
        return
    if not args.email or not args.password:
        raise SystemExit("Provide --email and --password, or use --token.")
    sess = requests.Session()
    res = sess.post(
        f"{base_url}/auth/login",
        json={"email": args.email, "password": args.password},
        timeout=15,
    )
    if not res.ok:
        raise SystemExit(f"Login failed: {res.status_code} {res.text}")
    cfg = {
        "base_url": base_url,
        "cookies": requests.utils.dict_from_cookiejar(sess.cookies),
    }
    _save_config(cfg)
    print("Login OK")


def cmd_logout(_: argparse.Namespace) -> None:
    cfg = _load_config()
    if not cfg.get("cookies"):
        print("Already logged out")
        return
    sess = _session_from_config(cfg)
    base_url = cfg.get("base_url", DEFAULT_BASE_URL)
    try:
        sess.post(f"{base_url}/auth/logout", timeout=10)
    except Exception:
        pass
    _save_config({})
    print("Logged out")


def cmd_whoami(_: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = cfg.get("base_url", DEFAULT_BASE_URL)
    res = sess.get(f"{base_url}/auth/me", timeout=15)
    if not res.ok:
        raise SystemExit(f"Auth check failed: {res.status_code} {res.text}")
    print(json.dumps(res.json(), indent=2))


def cmd_projects_list(_: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "GET", f"{base_url}/projects")
    print(json.dumps(data, indent=2))


def cmd_projects_create(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/projects", json={"name": args.name})
    print(json.dumps(data, indent=2))


def cmd_projects_get(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "GET", f"{base_url}/projects/{args.project_id}")
    print(json.dumps(data, indent=2))


def cmd_projects_archive(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/projects/{args.project_id}/archive")
    print(json.dumps(data, indent=2))


def cmd_projects_unarchive(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/projects/{args.project_id}/unarchive")
    print(json.dumps(data, indent=2))


def cmd_projects_api_keys_list(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "GET", f"{base_url}/projects/{args.project_id}/api-keys")
    print(json.dumps(data, indent=2))


def cmd_projects_api_keys_create(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/projects/{args.project_id}/api-keys")
    print(json.dumps(data, indent=2))


def cmd_projects_api_keys_revoke(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "DELETE", f"{base_url}/projects/{args.project_id}/api-keys/{args.key_id}")
    print(json.dumps(data, indent=2))


def cmd_projects_disable(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/projects/{args.project_id}/disable")
    print(json.dumps(data, indent=2))


def cmd_projects_enable(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/projects/{args.project_id}/enable")
    print(json.dumps(data, indent=2))


def cmd_projects_run(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/projects/{args.project_id}/run")
    print(json.dumps(data, indent=2))


def cmd_projects_stop(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/projects/{args.project_id}/stop")
    print(json.dumps(data, indent=2))


def cmd_projects_export_zip(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    res = sess.get(f"{base_url}/projects/{args.project_id}/export/zip", timeout=60, stream=True)
    if not res.ok:
        raise SystemExit(f"Failed to export: {res.status_code} {res.text}")
    out_path = args.out or f"project-{args.project_id}.zip"
    with open(out_path, "wb") as f:
        for chunk in res.iter_content(chunk_size=1024 * 1024):
            if chunk:
                f.write(chunk)
    print(json.dumps({"saved": out_path}, indent=2))


def cmd_project_data_get(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/projects/{args.project_id}/data/entry", json={"key": args.key})
    print(json.dumps(data, indent=2))


def cmd_project_data_set(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    payload = {"key": args.key, "value": json.loads(args.value)}
    data = _request(sess, "POST", f"{base_url}/projects/{args.project_id}/data/set", json=payload)
    print(json.dumps(data, indent=2))


def cmd_project_data_increment(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    payload = {"key": args.key, "amount": args.amount}
    data = _request(sess, "POST", f"{base_url}/projects/{args.project_id}/data/increment", json=payload)
    print(json.dumps(data, indent=2))


def cmd_project_data_delete(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/projects/{args.project_id}/data/delete", json={"key": args.key})
    print(json.dumps(data, indent=2))


def cmd_project_data_export(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    res = sess.get(f"{base_url}/projects/{args.project_id}/data/export", timeout=60)
    if not res.ok:
        raise SystemExit(f"Failed to export data: {res.status_code} {res.text}")
    print(json.dumps(res.json(), indent=2))


def cmd_templates_list(_: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "GET", f"{base_url}/templates")
    print(json.dumps(data, indent=2))


def cmd_templates_mine(_: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "GET", f"{base_url}/templates/mine")
    print(json.dumps(data, indent=2))


def cmd_templates_get(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "GET", f"{base_url}/templates/{args.template_id}")
    print(json.dumps(data, indent=2))


def cmd_templates_submit(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    payload = {"title": args.title, "summary": args.summary, "description": args.description}
    data = _request(sess, "POST", f"{base_url}/workflows/{args.workflow_id}/template", json=payload)
    print(json.dumps(data, indent=2))


def cmd_templates_withdraw(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/templates/{args.template_id}/withdraw")
    print(json.dumps(data, indent=2))


def cmd_templates_duplicate(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/templates/{args.template_id}/duplicate")
    print(json.dumps(data, indent=2))


def cmd_domains_list(_: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "GET", f"{base_url}/users/me/domains")
    print(json.dumps(data, indent=2))


def cmd_domains_add(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    payload = {"domain": args.domain}
    data = _request(sess, "POST", f"{base_url}/users/me/domains", json=payload)
    print(json.dumps(data, indent=2))


def cmd_domains_verify(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/users/me/domains/{args.request_id}/verify")
    print(json.dumps(data, indent=2))


def cmd_domains_delete(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "DELETE", f"{base_url}/users/me/domains/{args.request_id}")
    print(json.dumps(data, indent=2))


def cmd_domains_mailbox_request(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    payload = {"localPart": args.local_part, "domain": args.domain}
    data = _request(sess, "POST", f"{base_url}/users/me/domains/{args.request_id}/mailbox-request", json=payload)
    print(json.dumps(data, indent=2))


def cmd_cli_tokens_list(_: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "GET", f"{base_url}/auth/cli-tokens")
    print(json.dumps(data, indent=2))


def cmd_cli_tokens_create(_: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "POST", f"{base_url}/auth/cli-tokens")
    print(json.dumps(data, indent=2))


def cmd_cli_tokens_revoke(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = _base_url(cfg)
    data = _request(sess, "DELETE", f"{base_url}/auth/cli-tokens/{args.token_id}")
    print(json.dumps(data, indent=2))


def cmd_workflows_list(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = cfg.get("base_url", DEFAULT_BASE_URL)
    res = sess.get(f"{base_url}/projects/{args.project_id}/workflows", timeout=15)
    if not res.ok:
        raise SystemExit(f"Failed to list workflows: {res.status_code} {res.text}")
    print(json.dumps(res.json(), indent=2))


def cmd_workflows_publish(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = cfg.get("base_url", DEFAULT_BASE_URL)
    res = sess.post(f"{base_url}/workflows/{args.workflow_id}/publish", timeout=15)
    if not res.ok:
        raise SystemExit(f"Failed to publish workflow: {res.status_code} {res.text}")
    print(json.dumps(res.json(), indent=2))


def cmd_workflows_deploy(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = cfg.get("base_url", DEFAULT_BASE_URL)
    res = sess.post(f"{base_url}/workflows/{args.workflow_id}/deploy", timeout=15)
    if not res.ok:
        raise SystemExit(f"Failed to deploy workflow: {res.status_code} {res.text}")
    print(json.dumps(res.json(), indent=2))


def cmd_workflows_get(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = cfg.get("base_url", DEFAULT_BASE_URL)
    res = sess.get(f"{base_url}/workflows/{args.workflow_id}", timeout=15)
    if not res.ok:
        raise SystemExit(f"Failed to fetch workflow: {res.status_code} {res.text}")
    print(json.dumps(res.json(), indent=2))


def cmd_workflows_run(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = cfg.get("base_url", DEFAULT_BASE_URL)
    res = sess.post(f"{base_url}/workflows/{args.workflow_id}/run", timeout=15)
    if not res.ok:
        raise SystemExit(f"Failed to run workflow: {res.status_code} {res.text}")
    print(json.dumps(res.json(), indent=2))


def cmd_workflows_deployments(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = cfg.get("base_url", DEFAULT_BASE_URL)
    res = sess.get(f"{base_url}/workflows/{args.workflow_id}/deployments", timeout=15)
    if not res.ok:
        raise SystemExit(f"Failed to list deployments: {res.status_code} {res.text}")
    print(json.dumps(res.json(), indent=2))


def cmd_workflows_requirements(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = cfg.get("base_url", DEFAULT_BASE_URL)
    res = sess.get(f"{base_url}/workflows/{args.workflow_id}/requirements", timeout=15)
    if not res.ok:
        raise SystemExit(f"Failed to fetch requirements: {res.status_code} {res.text}")
    print(json.dumps(res.json(), indent=2))


def cmd_deployments_runs(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = cfg.get("base_url", DEFAULT_BASE_URL)
    res = sess.get(f"{base_url}/deployments/{args.deployment_id}/runs", timeout=15)
    if not res.ok:
        raise SystemExit(f"Failed to list runs: {res.status_code} {res.text}")
    print(json.dumps(res.json(), indent=2))


def cmd_runs_logs(args: argparse.Namespace) -> None:
    cfg = _load_config()
    sess = _require_session(cfg)
    base_url = cfg.get("base_url", DEFAULT_BASE_URL)
    res = sess.get(f"{base_url}/runs/{args.run_id}/logs", timeout=15)
    if not res.ok:
        raise SystemExit(f"Failed to fetch logs: {res.status_code} {res.text}")
    print(json.dumps(res.json(), indent=2))


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog="cxv")
    sub = parser.add_subparsers(dest="cmd", required=True)

    login = sub.add_parser("login")
    login.add_argument("--email")
    login.add_argument("--password")
    login.add_argument("--token")
    login.add_argument("--base-url")
    login.set_defaults(func=cmd_login)

    logout = sub.add_parser("logout")
    logout.set_defaults(func=cmd_logout)

    whoami = sub.add_parser("whoami")
    whoami.set_defaults(func=cmd_whoami)

    projects = sub.add_parser("projects")
    proj_sub = projects.add_subparsers(dest="subcmd", required=True)
    proj_list = proj_sub.add_parser("list")
    proj_list.set_defaults(func=cmd_projects_list)
    proj_create = proj_sub.add_parser("create")
    proj_create.add_argument("--name", required=True)
    proj_create.set_defaults(func=cmd_projects_create)
    proj_get = proj_sub.add_parser("get")
    proj_get.add_argument("--project-id", required=True)
    proj_get.set_defaults(func=cmd_projects_get)
    proj_archive = proj_sub.add_parser("archive")
    proj_archive.add_argument("--project-id", required=True)
    proj_archive.set_defaults(func=cmd_projects_archive)
    proj_unarchive = proj_sub.add_parser("unarchive")
    proj_unarchive.add_argument("--project-id", required=True)
    proj_unarchive.set_defaults(func=cmd_projects_unarchive)
    proj_disable = proj_sub.add_parser("disable")
    proj_disable.add_argument("--project-id", required=True)
    proj_disable.set_defaults(func=cmd_projects_disable)
    proj_enable = proj_sub.add_parser("enable")
    proj_enable.add_argument("--project-id", required=True)
    proj_enable.set_defaults(func=cmd_projects_enable)
    proj_run = proj_sub.add_parser("run")
    proj_run.add_argument("--project-id", required=True)
    proj_run.set_defaults(func=cmd_projects_run)
    proj_stop = proj_sub.add_parser("stop")
    proj_stop.add_argument("--project-id", required=True)
    proj_stop.set_defaults(func=cmd_projects_stop)
    proj_export = proj_sub.add_parser("export-zip")
    proj_export.add_argument("--project-id", required=True)
    proj_export.add_argument("--out")
    proj_export.set_defaults(func=cmd_projects_export_zip)

    proj_keys = proj_sub.add_parser("api-keys")
    proj_keys_sub = proj_keys.add_subparsers(dest="keycmd", required=True)
    proj_keys_list = proj_keys_sub.add_parser("list")
    proj_keys_list.add_argument("--project-id", required=True)
    proj_keys_list.set_defaults(func=cmd_projects_api_keys_list)
    proj_keys_create = proj_keys_sub.add_parser("create")
    proj_keys_create.add_argument("--project-id", required=True)
    proj_keys_create.set_defaults(func=cmd_projects_api_keys_create)
    proj_keys_revoke = proj_keys_sub.add_parser("revoke")
    proj_keys_revoke.add_argument("--project-id", required=True)
    proj_keys_revoke.add_argument("--key-id", required=True)
    proj_keys_revoke.set_defaults(func=cmd_projects_api_keys_revoke)

    proj_data = proj_sub.add_parser("data")
    proj_data_sub = proj_data.add_subparsers(dest="datacmd", required=True)
    proj_data_get = proj_data_sub.add_parser("get")
    proj_data_get.add_argument("--project-id", required=True)
    proj_data_get.add_argument("--key", required=True)
    proj_data_get.set_defaults(func=cmd_project_data_get)
    proj_data_set = proj_data_sub.add_parser("set")
    proj_data_set.add_argument("--project-id", required=True)
    proj_data_set.add_argument("--key", required=True)
    proj_data_set.add_argument("--value", required=True, help="JSON string")
    proj_data_set.set_defaults(func=cmd_project_data_set)
    proj_data_inc = proj_data_sub.add_parser("increment")
    proj_data_inc.add_argument("--project-id", required=True)
    proj_data_inc.add_argument("--key", required=True)
    proj_data_inc.add_argument("--amount", type=float, required=True)
    proj_data_inc.set_defaults(func=cmd_project_data_increment)
    proj_data_del = proj_data_sub.add_parser("delete")
    proj_data_del.add_argument("--project-id", required=True)
    proj_data_del.add_argument("--key", required=True)
    proj_data_del.set_defaults(func=cmd_project_data_delete)
    proj_data_export = proj_data_sub.add_parser("export")
    proj_data_export.add_argument("--project-id", required=True)
    proj_data_export.set_defaults(func=cmd_project_data_export)

    workflows = sub.add_parser("workflows")
    wf_sub = workflows.add_subparsers(dest="subcmd", required=True)
    wf_list = wf_sub.add_parser("list")
    wf_list.add_argument("--project-id", required=True)
    wf_list.set_defaults(func=cmd_workflows_list)
    wf_get = wf_sub.add_parser("get")
    wf_get.add_argument("--workflow-id", required=True)
    wf_get.set_defaults(func=cmd_workflows_get)
    wf_pub = wf_sub.add_parser("publish")
    wf_pub.add_argument("--workflow-id", required=True)
    wf_pub.set_defaults(func=cmd_workflows_publish)
    wf_dep = wf_sub.add_parser("deploy")
    wf_dep.add_argument("--workflow-id", required=True)
    wf_dep.set_defaults(func=cmd_workflows_deploy)
    wf_run = wf_sub.add_parser("run")
    wf_run.add_argument("--workflow-id", required=True)
    wf_run.set_defaults(func=cmd_workflows_run)
    wf_deployments = wf_sub.add_parser("deployments")
    wf_deployments.add_argument("--workflow-id", required=True)
    wf_deployments.set_defaults(func=cmd_workflows_deployments)
    wf_requirements = wf_sub.add_parser("requirements")
    wf_requirements.add_argument("--workflow-id", required=True)
    wf_requirements.set_defaults(func=cmd_workflows_requirements)

    deployments = sub.add_parser("deployments")
    dep_sub = deployments.add_subparsers(dest="subcmd", required=True)
    dep_runs = dep_sub.add_parser("runs")
    dep_runs.add_argument("--deployment-id", required=True)
    dep_runs.set_defaults(func=cmd_deployments_runs)

    runs = sub.add_parser("runs")
    runs_sub = runs.add_subparsers(dest="subcmd", required=True)
    runs_logs = runs_sub.add_parser("logs")
    runs_logs.add_argument("--run-id", required=True)
    runs_logs.set_defaults(func=cmd_runs_logs)

    templates = sub.add_parser("templates")
    tpl_sub = templates.add_subparsers(dest="subcmd", required=True)
    tpl_list = tpl_sub.add_parser("list")
    tpl_list.set_defaults(func=cmd_templates_list)
    tpl_mine = tpl_sub.add_parser("mine")
    tpl_mine.set_defaults(func=cmd_templates_mine)
    tpl_get = tpl_sub.add_parser("get")
    tpl_get.add_argument("--template-id", required=True)
    tpl_get.set_defaults(func=cmd_templates_get)
    tpl_submit = tpl_sub.add_parser("submit")
    tpl_submit.add_argument("--workflow-id", required=True)
    tpl_submit.add_argument("--title", required=True)
    tpl_submit.add_argument("--summary", required=True)
    tpl_submit.add_argument("--description", required=True)
    tpl_submit.set_defaults(func=cmd_templates_submit)
    tpl_withdraw = tpl_sub.add_parser("withdraw")
    tpl_withdraw.add_argument("--template-id", required=True)
    tpl_withdraw.set_defaults(func=cmd_templates_withdraw)
    tpl_dup = tpl_sub.add_parser("duplicate")
    tpl_dup.add_argument("--template-id", required=True)
    tpl_dup.set_defaults(func=cmd_templates_duplicate)

    domains = sub.add_parser("domains")
    dom_sub = domains.add_subparsers(dest="subcmd", required=True)
    dom_list = dom_sub.add_parser("list")
    dom_list.set_defaults(func=cmd_domains_list)
    dom_add = dom_sub.add_parser("add")
    dom_add.add_argument("--domain", required=True)
    dom_add.set_defaults(func=cmd_domains_add)
    dom_verify = dom_sub.add_parser("verify")
    dom_verify.add_argument("--request-id", required=True)
    dom_verify.set_defaults(func=cmd_domains_verify)
    dom_delete = dom_sub.add_parser("delete")
    dom_delete.add_argument("--request-id", required=True)
    dom_delete.set_defaults(func=cmd_domains_delete)
    dom_mailbox = dom_sub.add_parser("mailbox-request")
    dom_mailbox.add_argument("--request-id", required=True)
    dom_mailbox.add_argument("--domain", required=True)
    dom_mailbox.add_argument("--local-part", required=True)
    dom_mailbox.set_defaults(func=cmd_domains_mailbox_request)

    cli_tokens = sub.add_parser("cli-tokens")
    cli_tokens_sub = cli_tokens.add_subparsers(dest="subcmd", required=True)
    cli_tokens_list = cli_tokens_sub.add_parser("list")
    cli_tokens_list.set_defaults(func=cmd_cli_tokens_list)
    cli_tokens_create = cli_tokens_sub.add_parser("create")
    cli_tokens_create.set_defaults(func=cmd_cli_tokens_create)
    cli_tokens_revoke = cli_tokens_sub.add_parser("revoke")
    cli_tokens_revoke.add_argument("--token-id", required=True)
    cli_tokens_revoke.set_defaults(func=cmd_cli_tokens_revoke)

    return parser


def main() -> None:
    parser = build_parser()
    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()
