// P9 slice-5 DLQ smoke: a poison chaos event is dead-lettered (not lost), shows up
// tenant-scoped in /v1/dlq, another tenant can neither see nor replay it, and the
// owner's replay resolves it (KG-13 / KG-06). Requires the service running with
// IIOS_DEV_TOKENS=1 (the relay ticks by default, publishing the poison to the consumer).
import 'dotenv/config';

const SERVICE = process.env.SMOKE_URL ?? 'http://localhost:3200';
const APP_ID = 'portal-demo';
const assert = (c, m) => { if (!c) { console.error('✗', m); process.exit(1); } console.log('✓', m); };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function devToken(userId, orgId) {
  const r = await fetch(`${SERVICE}/v1/dev/token`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ appId: APP_ID, userId, name: userId, orgId }),
  });
  if (!r.ok) throw new Error(`devToken ${r.status} (service with IIOS_DEV_TOKENS=1?)`);
  return (await r.json()).token;
}
async function call(token, path, method = 'GET', body) {
  return fetch(`${SERVICE}${path}`, {
    method,
    headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
    body: body ? JSON.stringify(body) : undefined,
  });
}
async function json(token, path, method = 'GET', body) {
  const r = await call(token, path, method, body);
  if (!r.ok) throw new Error(`${method} ${path} ${r.status}: ${await r.text()}`);
  return r.json();
}

const A = await devToken('a1', 'org_A');
const B = await devToken('b1', 'org_B');

// A injects a poison chaos event.
const { eventId } = await json(A, '/v1/dev/chaos/poison', 'POST');
assert(eventId.startsWith('evt_chaos_'), `A injected a poison chaos event (${eventId})`);

// Poll until the relay publishes it and the chaos consumer dead-letters it.
let item;
for (let i = 0; i < 40; i++) {
  const list = await json(A, '/v1/dlq');
  item = list.find((d) => d.sourceId === eventId);
  if (item) break;
  await sleep(250);
}
assert(item, 'poison surfaced in A\'s DLQ (relay published → consumer failed → dead-lettered)');
assert(item.status === 'QUARANTINED', `DLQ item is QUARANTINED (got ${item.status})`);
assert(item.consumerName === 'chaos-consumer', `dead-lettered by the chaos-consumer lane (got ${item.consumerName})`);

// Tenant B cannot see A's DLQ item.
const bList = await json(B, '/v1/dlq');
assert(!bList.some((d) => d.sourceId === eventId), "tenant B's DLQ excludes A's item (isolation)");

// Tenant B cannot replay A's DLQ item.
const bReplay = await call(B, `/v1/dlq/${item.id}/replay`, 'POST');
assert(bReplay.status === 403, `tenant B replaying A's item → 403 (got ${bReplay.status}) — KG-02`);

// A replays → resolves.
const replayed = await json(A, `/v1/dlq/${item.id}/replay`, 'POST');
assert(replayed.status === 'RESOLVED', `A's replay resolves the poison (got ${replayed.status})`);

// It now shows RESOLVED in A's DLQ.
const afterList = await json(A, '/v1/dlq');
const after = afterList.find((d) => d.sourceId === eventId);
assert(after && after.status === 'RESOLVED', 'DLQ item is RESOLVED after replay');

console.log('\nP9 DLQ smoke: PASS');
process.exit(0);
