#!/usr/bin/env node
/**
 * Test standalone des nouvelles fonctionnalités RPC du wrapper.
 *
 * Lance un OverBridgeServer sur :3100 qui pointe vers le MCP live (:3099)
 * et exerce chaque méthode RPC documentée.
 *
 * Usage: node scripts/test-wrapper-rpc.mjs
 */

import { OverBridgeService, OverBridgeServer } from '../dist/bridge/index.js';

const WRAPPER_PORT = 3100;
const UPSTREAM_MCP = process.env.MCP_URL || 'http://localhost:3099/mcp';
const BASE = `http://127.0.0.1:${WRAPPER_PORT}`;
let pass = 0;
let fail = 0;
const results = [];

const service = new OverBridgeService(
  { mcpUrl: UPSTREAM_MCP, defaultTimeoutMs: 60_000, agentTimeoutMs: 180_000 },
  {
    debug: (m) => console.log(`  [debug] ${m}`),
    info: (m) => console.log(`  [info] ${m}`),
    warn: (m) => console.log(`  [warn] ${m}`),
    error: (m) => console.log(`  [error] ${m}`),
  },
);

const server = new OverBridgeServer(
  service,
  {
    port: WRAPPER_PORT,
    host: '127.0.0.1',
    postgres: {
      host: 'localhost', port: 5432, user: 'postgres', password: '', database: 'overmind_memory',
    },
    enableMessageLog: false,    // on désactive MessageLog (pas de Postgres ici)
    enableSessionStore: true,    // multi-tenant (in-memory)
    enableDirectives: true,
    enableWebhooks: true,
    healthCheckIntervalMs: 0,    // pas de heartbeat
    jsonBodyLimit: '5mb',
  },
  {
    debug: () => {},
    info: (m) => console.log(`  [server] ${m}`),
    warn: (m) => console.log(`  [server.warn] ${m}`),
    error: (m) => console.log(`  [server.error] ${m}`),
  },
);

async function rpc(method, params = {}, id = 1) {
  const res = await fetch(`${BASE}/rpc`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
  });
  return await res.json();
}

function check(name, ok, detail = '') {
  if (ok) { pass++; results.push(`  PASS  ${name}${detail ? ' - ' + detail : ''}`); }
  else    { fail++; results.push(`  FAIL  ${name}${detail ? ' - ' + detail : ''}`); }
}

async function main() {
  console.log('═══════════════════════════════════════════════════════');
  console.log('  Test Standalone - Wrapper RPC');
  console.log(`  Wrapper: ${BASE}/rpc`);
  console.log(`  Upstream MCP: ${UPSTREAM_MCP}`);
  console.log('═══════════════════════════════════════════════════════');

  await server.start();
  console.log('');

  // ─── 1. health.ping ──────────────────────────────────────────
  {
    const r = await rpc('health.ping');
    check('health.ping', r.result?.pong === true, `pong=${r.result?.pong}`);
  }

  // ─── 2. agent.list (filtre runner=hermes) ───────────────────
  {
    const r = await rpc('agent.list', { runner: 'hermes' });
    const agents = JSON.parse(r.result?.agents?.[0] || '[]');
    // Le retour est wrappé dans content[0].text (format MCP).
    // On accepte si pas d'erreur.
    check('agent.list (runner=hermes)', !r.error, `agents.length=${r.result?.stats?.total}`);
  }

  // ─── 3. agent.status (state local) ──────────────────────────
  {
    const r = await rpc('agent.status', { agentName: 'sniperbot_analyst' });
    const hasLocal = r.result?.local !== undefined;
    check('agent.status', hasLocal, hasLocal ? `status=${r.result.local.status}` : JSON.stringify(r).slice(0,200));
  }

  // ─── 4. agent.run AVEC externalKey (SessionStore) ──────────
  {
    const r = await rpc('agent.run', {
      agentName: 'sniperbot_analyst',
      runner: 'hermes',
      prompt: 'Test SessionStore — reponds juste OK',
      externalKey: 'sniper-test-phone-001',
    });
    const hasSession = r.result?.sessionId !== undefined;
    check('agent.run + externalKey', hasSession && !r.error?.message?.includes('Invalid'),
      `sessionId=${r.result?.sessionId}, content[0]=${r.result?.content?.[0]?.text?.slice(0,80)}`);
  }

  // ─── 5. session.get ───────────────────────────────────────
  {
    const r = await rpc('session.get', {
      externalKey: 'sniper-test-phone-001',
      agentName: 'sniperbot_analyst',
    });
    const found = r.result?.session !== null && r.result?.session !== undefined;
    check('session.get', found, `sessionId=${r.result?.session?.sessionId?.slice(0,20)}...`);
  }

  // ─── 6. session.list ──────────────────────────────────────
  {
    const r = await rpc('session.list', {});
    const list = r.result?.sessions || [];
    check('session.list', Array.isArray(list) && list.length > 0, `count=${list.length}`);
  }

  // ─── 7. session.stats ─────────────────────────────────────
  {
    const r = await rpc('session.stats', {});
    const total = r.result?.total;
    check('session.stats', typeof total === 'number', `total=${total}, byAgent=${JSON.stringify(r.result?.byAgent)}`);
  }

  // ─── 8. webhook.sms avec autoDispatch (voipms → sniperbot) ─
  {
    const r = await rpc('webhook.sms', {
      provider: 'voipms',
      payload: {
        from: '+14181234567',
        to: '+14187654321',
        message: 'BTC test via webhook',
        id: 'msg-test-001',
        date: new Date().toISOString(),
      },
      externalKey: 'sniper-test-phone-001',
      autoDispatch: {
        agentName: 'sniperbot_analyst',
        runner: 'hermes',
      },
    });
    const dispatched = r.result?.messageId !== undefined || r.result?.content !== undefined;
    check('webhook.sms + autoDispatch', dispatched || !r.error,
      dispatched ? `messageId=${r.result?.messageId?.slice(0,8)}...` : JSON.stringify(r).slice(0,200));
  }

  // ─── 9. batch RPC (array de 3 requêtes) ───────────────────
  {
    const batch = await fetch(`${BASE}/rpc`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify([
        { jsonrpc: '2.0', id: 1, method: 'health.ping', params: {} },
        { jsonrpc: '2.0', id: 2, method: 'health.ping', params: {} },
        { jsonrpc: '2.0', id: 3, method: 'health.ping', params: {} },
      ]),
    });
    const arr = await batch.json();
    const allOk = Array.isArray(arr) && arr.length === 3 && arr.every((r) => r.result?.pong === true);
    check('batch RPC (3 requêtes)', allOk, `count=${arr.length}`);
  }

  // ─── 10. method unknown → -32601 ──────────────────────────
  {
    const r = await rpc('does.not.exist', {});
    check('method unknown → -32601', r.error?.code === -32601, `code=${r.error?.code}`);
  }

  // ─── 11. params invalides → -32602 ────────────────────────
  {
    const r = await rpc('agent.run', {});  // manque agentName, runner, prompt
    check('params invalides → -32602', r.error?.code === -32602, `code=${r.error?.code}, issues=${r.error?.data?.length || 0}`);
  }

  // ─── 12. /health endpoint HTTP ────────────────────────────
  {
    const r = await fetch(`${BASE}/health`).then((r) => r.json());
    const hasFields = r.status && r.mcp && r.features !== undefined;
    check('GET /health', hasFields, `status=${r.status}, features.sessionStore=${r.features?.sessionStore}`);
  }

  // ─── 13. /webhook/:provider endpoint HTTP ───────────────────
  {
    const r = await fetch(`${BASE}/webhook/voipms`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ from: '+14181112222', message: 'hi', id: 'm1' }),
    });
    const j = await r.json();
    check('POST /webhook/voipms', j.received === true, `provider=${j.provider}, externalKey=${j.externalKey}`);
  }

  // ─── 14. path traversal bloqué sur /f/ ────────────────────
  {
    const r = await fetch(`${BASE}/f/..%2Fetc%2Fpasswd`);
    const status = r.status;
    check('path traversal bloqué', status === 403 || status === 400, `status=${status}`);
  }

  // ─── 15. session.delete ───────────────────────────────────
  {
    const r = await rpc('session.delete', {
      externalKey: 'sniper-test-phone-001',
      agentName: 'sniperbot_analyst',
    });
    check('session.delete', r.result?.deleted === true, `deleted=${r.result?.deleted}`);
  }

  // ─── Cleanup ─────────────────────────────────────────────
  await server.stop();
  console.log('');
  console.log('═══════════════════════════════════════════════════════');
  for (const line of results) console.log(line);
  console.log('═══════════════════════════════════════════════════════');
  console.log(`  ${pass} pass, ${fail} fail`);
  console.log('═══════════════════════════════════════════════════════');
  process.exit(fail > 0 ? 1 : 0);
}

main().catch((err) => {
  console.error('FATAL:', err);
  process.exit(1);
});
