/**
 * # cli/keypair — the registered-agent Ed25519 keypair + request-JWT minter
 * (kestrel-markets-0t0 SLICE 2, PLAT-ADR-0021).
 *
 * The client half of the AAP keypair rung. `kestrel register --keypair` generates an
 * Ed25519 keypair, enrolls the PUBLIC half with the platform, and stores the PRIVATE
 * half locally (0600, cli/credentials.ts). Thereafter the CLI authenticates by minting
 * a SHORT-LIVED (≤5 min), single-use JWT it signs with the private key — a possession
 * proof DISTINCT from replaying the durable bearer capability. The private key NEVER
 * leaves the machine; only freshly-minted, minutes-long assertions cross the wire, so a
 * captured request is worthless in minutes and useless on replay (the platform refuses a
 * reused `jti`).
 *
 * Dependency-light on purpose: WebCrypto (`globalThis.crypto.subtle`, node ≥18 / bun) +
 * node's `Buffer` for base64url — no third-party crypto, matching the platform's
 * native-Ed25519 rule. The JWT wire format mirrors the platform verifier (agents/jwt.ts):
 * header {alg:"EdDSA",typ:"JWT"}, claims { sub, aud, iat, exp, jti }.
 */

/** An Ed25519 public JWK (OKP) — the half enrolled with the platform. */
export interface Ed25519PublicJwk {
  readonly kty: "OKP";
  readonly crv: "Ed25519";
  readonly x: string;
}

/** An Ed25519 private JWK (OKP) — held locally (0600), never sent. */
export interface Ed25519PrivateJwk extends Ed25519PublicJwk {
  readonly d: string;
}

const ED25519 = { name: "Ed25519" } as const;

function subtle(): SubtleCrypto {
  const c = (globalThis as { crypto?: Crypto }).crypto;
  if (!c?.subtle) {
    throw new Error("WebCrypto (crypto.subtle) unavailable — need node ≥18 or bun to use --keypair / JWT auth");
  }
  return c.subtle;
}

function b64url(bytes: Uint8Array): string {
  return Buffer.from(bytes).toString("base64url");
}

/** Minimal structural view of an exported JWK (the OSS tsconfig lib omits the global type). */
type JwkExport = { x?: string; d?: string };

/** Generate a fresh Ed25519 keypair as JWKs (public + private halves). */
export async function generateAgentKeypair(): Promise<{ publicJwk: Ed25519PublicJwk; privateJwk: Ed25519PrivateJwk }> {
  const kp = (await subtle().generateKey(ED25519, true, ["sign", "verify"])) as CryptoKeyPair;
  const priv = (await subtle().exportKey("jwk", kp.privateKey)) as JwkExport;
  const pub = (await subtle().exportKey("jwk", kp.publicKey)) as JwkExport;
  return {
    publicJwk: { kty: "OKP", crv: "Ed25519", x: pub.x! },
    privateJwk: { kty: "OKP", crv: "Ed25519", x: priv.x!, d: priv.d! },
  };
}

/** The claims a request JWT carries (times in SECONDS per RFC 7519). */
export interface RequestJwtClaims {
  readonly sub: string;
  readonly aud: string;
  readonly iat: number;
  readonly exp: number;
  readonly jti: string;
}

export interface MintRequestJwtInput {
  readonly privateJwk: Ed25519PrivateJwk;
  /** The agent's subject (the platform looks the enrolled key up by it). */
  readonly subject: string;
  /** The audience the platform expects (stored from the enrollment response). */
  readonly audience: string;
  /** TTL in seconds (≤ 300; the platform refuses a longer span). Default 120. */
  readonly ttlSec?: number;
  /** Current time (epoch ms). Default `Date.now()`. */
  readonly now?: number;
}

/**
 * Mint a fresh, short-lived, single-use request JWT signed with the agent's private key.
 * Each call yields a UNIQUE `jti` (via `crypto.randomUUID`) and a fresh `iat`/`exp`, so
 * successive requests never collide with the platform's replay guard.
 */
export async function mintRequestJwt(input: MintRequestJwtInput): Promise<string> {
  const nowMs = input.now ?? Date.now();
  const iat = Math.floor(nowMs / 1000);
  const ttl = Math.min(Math.max(input.ttlSec ?? 120, 1), 300);
  const claims: RequestJwtClaims = {
    sub: input.subject,
    aud: input.audience,
    iat,
    exp: iat + ttl,
    jti: `jwt_${(globalThis.crypto as Crypto).randomUUID().replace(/-/g, "")}`,
  };
  const key = await subtle().importKey("jwk", { ...input.privateJwk }, ED25519, false, ["sign"]);
  const headerB64 = b64url(Buffer.from(JSON.stringify({ alg: "EdDSA", typ: "JWT" })));
  const payloadB64 = b64url(Buffer.from(JSON.stringify(claims)));
  const signingInput = `${headerB64}.${payloadB64}`;
  const sig = await subtle().sign(ED25519, key, Buffer.from(signingInput));
  return `${signingInput}.${b64url(new Uint8Array(sig))}`;
}
