#!/usr/bin/env node
/**
 * fabric-register — bind an OpenClaw agent to a Fabric Center API key.
 *
 * One-time, self-contained (no plugin deps). Installed to
 * ~/.openclaw/bin/fabric-register by the plugin installer.
 *
 *   AGENT_ID is read from the environment if set; otherwise --agent-id is
 *   required. The API key is validated against Center (POST
 *   /auth/agent/login) and, on success, written to the plugin's identity
 *   file so the Fabric channel plugin can connect that agent.
 *
 * Usage:
 *   fabric-register --api-key fak_xxx                 # uses $AGENT_ID
 *   fabric-register --agent-id echo --api-key fak_xxx
 *
 * Only AGENT_ID is read from the environment; everything else is a flag.
 *
 * Flags:
 *   --agent-id <id>        required unless the AGENT_ID env var is set
 *   --api-key <fak_…>      required (flag only — never from the environment)
 *   --center <url>         else openclaw.json channels.fabric.centerApiBase;
 *                          else http://localhost:7001/api
 *   --identity-file <path> default ~/.openclaw/fabric-identity.json
 *   --openclaw-path <dir>  default ~/.openclaw
 *   -h | --help
 */
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { homedir } from 'node:os';

function parseArgs(argv) {
  const out = {};
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (a === '-h' || a === '--help') out.help = true;
    else if (a.startsWith('--')) {
      const key = a.slice(2);
      const v = argv[i + 1];
      if (v === undefined || v.startsWith('--')) out[key] = true;
      else {
        out[key] = v;
        i++;
      }
    }
  }
  return out;
}

const HELP = `fabric-register — bind an OpenClaw agent to a Fabric Center API key

  fabric-register --api-key fak_xxx                 # agent id from $AGENT_ID
  fabric-register --agent-id <id> --api-key fak_xxx

  --agent-id <id>         required unless the AGENT_ID env var is set
  --api-key <fak_…>       required (flag only — never read from the env)
  --center <url>          Center API base (else openclaw.json
                          channels.fabric.centerApiBase; else
                          http://localhost:7001/api)
  --identity-file <path>  default ~/.openclaw/fabric-identity.json
  --openclaw-path <dir>   default ~/.openclaw
  -h, --help

Only AGENT_ID is taken from the environment; everything else is a flag.
`;

function fail(msg) {
  console.error(`fabric-register: ${msg}`);
  process.exit(2);
}

async function main() {
  const a = parseArgs(process.argv.slice(2));
  if (a.help) {
    process.stdout.write(HELP);
    return;
  }

  // agent id: env AGENT_ID wins; else --agent-id is required.
  const agentId =
    (process.env.AGENT_ID && process.env.AGENT_ID.trim()) ||
    (typeof a['agent-id'] === 'string' && a['agent-id'].trim());
  if (!agentId) {
    fail('no agent id: set the AGENT_ID environment variable or pass --agent-id <id>');
  }

  // api key: flag ONLY — never from the environment.
  const apiKey = typeof a['api-key'] === 'string' && a['api-key'].trim();
  if (!apiKey) fail('missing --api-key <fak_…> (flag only)');

  const openclawPath = resolve(
    (typeof a['openclaw-path'] === 'string' && a['openclaw-path']) ||
      join(homedir(), '.openclaw'),
  );

  // center api base: flag > openclaw.json > default
  let center = (typeof a.center === 'string' && a.center) || '';
  if (!center) {
    try {
      const cfg = JSON.parse(readFileSync(join(openclawPath, 'openclaw.json'), 'utf8'));
      center = cfg?.channels?.fabric?.centerApiBase || '';
    } catch {
      /* fall through to default */
    }
  }
  if (!center) center = 'http://localhost:7001/api';
  center = center.replace(/\/+$/, '');

  const identityFile = resolve(
    (typeof a['identity-file'] === 'string' && a['identity-file']) ||
      join(openclawPath, 'fabric-identity.json'),
  );

  // 1) validate the key against Center (also resolves the Fabric identity)
  let session;
  try {
    const res = await fetch(`${center}/auth/agent/login`, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ apiKey }),
    });
    if (!res.ok) {
      const t = await res.text().catch(() => '');
      fail(`Center rejected the key: POST ${center}/auth/agent/login -> ${res.status} ${t}`);
    }
    session = await res.json();
  } catch (e) {
    fail(`could not reach Center at ${center}: ${e?.message || e}`);
  }

  // 2) upsert into the identity file (merge by agentId)
  let file = { entries: [] };
  if (existsSync(identityFile)) {
    try {
      const parsed = JSON.parse(readFileSync(identityFile, 'utf8'));
      if (parsed && Array.isArray(parsed.entries)) file = parsed;
    } catch {
      /* corrupt -> overwrite */
    }
  }
  const entry = {
    agentId,
    fabricApiKey: apiKey,
    fabricUserId: session?.user?.id,
    displayName: session?.user?.name,
  };
  const i = file.entries.findIndex((e) => e && e.agentId === agentId);
  if (i >= 0) file.entries[i] = { ...file.entries[i], ...entry };
  else file.entries.push(entry);

  mkdirSync(dirname(identityFile), { recursive: true });
  writeFileSync(identityFile, JSON.stringify(file, null, 2));

  console.log(
    `fabric-register: bound agent "${agentId}" -> Fabric user ` +
      `${session?.user?.email || session?.user?.id} (${identityFile}). ` +
      `Restart the gateway to connect: openclaw gateway restart`,
  );
}

main().catch((e) => fail(e?.message || String(e)));
