#!/usr/bin/env node

import http from 'node:http';
import OpenAI from 'openai';
import * as ed from '@noble/ed25519';
import {
  InMemoryDecisionLogStore,
  verifyChain,
} from '@agentguard-run/spend';
import { withSpendGuardOpenAI } from '@agentguard-run/spend/frameworks/openai';
import { withSpendGuardOpenRouter } from '@agentguard-run/spend/frameworks/openrouter';

const target = process.argv[2];
if (target !== 'openrouter' && target !== 'litellm') {
  throw new Error('usage: node sdk-openai-compatible.mjs <openrouter|litellm>');
}

const local = process.env.AG_ADAPTER_BASE_URL
  ? null
  : await startOpenAICompatibleServer();
const baseURL = process.env.AG_ADAPTER_BASE_URL || local.baseURL;
const privateKey = ed.utils.randomSecretKey();
const publicKey = await ed.getPublicKeyAsync(privateKey);
const logStore = new InMemoryDecisionLogStore();
const scope = { tenantId: `${target}-scratch`, agentId: 'adoption-smoke' };

function policy({ capCents, requiredCapability }) {
  return {
    id: `${target}-verified-policy`,
    name: `${target} verified policy`,
    scope: { tenantId: scope.tenantId },
    caps: [{ amountCents: capCents, window: 'per_call', action: 'block' }],
    mode: 'enforce',
    requiredCapability,
    version: 1,
    effectiveFrom: '2026-01-01T00:00:00.000Z',
  };
}

function clientFor({ capCents, requiredCapability, capabilityClaim }) {
  const raw = new OpenAI({ apiKey: 'scratch-only', baseURL });
  let dispatches = 0;
  const create = raw.chat.completions.create.bind(raw.chat.completions);
  raw.chat.completions.create = (...args) => {
    dispatches += 1;
    return create(...args);
  };
  const options = {
    policy: policy({ capCents, requiredCapability }),
    scope,
    capabilityClaim,
    config: { signingKeys: { privateKey, publicKey }, logStore },
  };
  const guarded = target === 'openrouter'
    ? withSpendGuardOpenRouter(raw, options)
    : withSpendGuardOpenAI(raw, options);
  return { guarded, dispatches: () => dispatches };
}

const request = {
  model: 'openai/gpt-4o-mini',
  messages: [{ role: 'user', content: 'Return the word bounded.' }],
  max_tokens: 50,
};

const allowed = clientFor({
  capCents: 10,
  requiredCapability: 'read_only',
  capabilityClaim: 'payment_execute',
});
const allowBefore = logStore.snapshot().length;
await allowed.guarded.chat.completions.create(request);
const allowReceipt = logStore.snapshot()[allowBefore];

const capped = clientFor({
  capCents: 1,
  requiredCapability: 'read_only',
  capabilityClaim: 'payment_execute',
});
const capBefore = logStore.snapshot().length;
let capDecision;
try {
  await capped.guarded.chat.completions.create(request);
  throw new Error('spend cap test unexpectedly dispatched');
} catch (error) {
  capDecision = error?.decision;
  if (!capDecision || capDecision.action !== 'block') throw error;
}
const capReceipt = logStore.snapshot()[capBefore];

const capability = clientFor({
  capCents: 10,
  requiredCapability: 'payment_execute',
  capabilityClaim: 'read_only',
});
const capabilityBefore = logStore.snapshot().length;
let capabilityDecision;
try {
  await capability.guarded.chat.completions.create(request);
  throw new Error('capability test unexpectedly dispatched');
} catch (error) {
  capabilityDecision = error?.decision;
  if (!capabilityDecision || capabilityDecision.action !== 'block') throw error;
}
const capabilityReceipt = logStore.snapshot()[capabilityBefore];

const entries = logStore.snapshot();
const verified = await verifyChain(entries, publicKey);
const tampered = structuredClone(entries);
tampered[0].decision.action = 'block';
const tamperCheck = await verifyChain(tampered, publicKey);

console.log(`TARGET ${target === 'openrouter' ? 'OpenRouter' : 'LiteLLM'}`);
console.log('SDK @agentguard-run/spend@0.15.13');
console.log(
  `ALLOWED action=${allowReceipt.decision.action} projected=${allowReceipt.decision.projectedCents}c ` +
  `dispatched=${allowed.dispatches()} seq=${allowReceipt.sequence} entryHash=${allowReceipt.entryHash}`,
);
console.log(
  `SIGNED signer=${allowReceipt.signerFingerprint} signaturePrefix=${allowReceipt.signature.slice(0, 24)}`,
);
console.log(
  `SPEND_CAP action=${capDecision.action} dispatched=${capped.dispatches()} seq=${capReceipt.sequence} ` +
  `reason=${JSON.stringify(capDecision.reasons[0])}`,
);
console.log(
  `CAPABILITY_CEILING action=${capabilityDecision.action} dispatched=${capability.dispatches()} ` +
  `seq=${capabilityReceipt.sequence} reason=${JSON.stringify(capabilityDecision.reasons[0])}`,
);
console.log(`OFFLINE_VERIFY valid=${verified.ok} entries=${entries.length}`);
console.log(`TAMPER_VERIFY valid=${tamperCheck.ok} reason=${JSON.stringify(tamperCheck.reason || '')}`);

if (local) {
  local.server.closeAllConnections?.();
  await new Promise((resolve) => local.server.close(resolve));
}

async function startOpenAICompatibleServer() {
  const server = http.createServer((req, res) => {
    let raw = '';
    req.setEncoding('utf8');
    req.on('data', (chunk) => {
      raw += chunk;
    });
    req.on('end', () => {
      JSON.parse(raw || '{}');
      res.writeHead(200, { 'content-type': 'application/json' });
      res.end(JSON.stringify({
        id: 'chatcmpl-agentguard-scratch',
        object: 'chat.completion',
        created: 1,
        model: 'openai/gpt-4o-mini',
        choices: [{
          index: 0,
          message: { role: 'assistant', content: 'bounded' },
          finish_reason: 'stop',
        }],
        usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
      }));
    });
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  const address = server.address();
  return { server, baseURL: `http://127.0.0.1:${address.port}/v1` };
}
