// P9 slice-8 DSR smoke: right-to-erasure redacts a subject's PII in place (a ticket
// subject), an active compliance hold blocks erasure until released, erasure is
// idempotent, and holds are tenant-fenced (KG-03 / KG-02). Requires the service
// running with IIOS_DEV_TOKENS=1.
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); };

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;
}
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 ticketSubject = async (token, id) => (await json(token, '/v1/support/tickets')).find((t) => t.id === id)?.subject;

const A = await devToken('dsr-a', 'org_dsr_A');
const B = await devToken('dsr-b', 'org_dsr_B');
const PII = 'my SSN is 123-45';

// A creates a ticket carrying PII.
const ticket = await json(A, '/v1/support/tickets', 'POST', { subject: PII });
assert((await ticketSubject(A, ticket.id)) === PII, 'ticket carries the subject PII');

// A places a compliance hold on their own subject.
const hold = await json(A, '/v1/dsr/holds', 'POST', { targetType: 'data_subject', targetId: 'self', reason: 'litigation' });
assert((await json(A, '/v1/dsr/holds')).some((h) => h.id === hold.id), 'compliance hold is listed');

// Erasure is blocked while the hold is active.
const blocked = await call(A, '/v1/dsr/erase', 'POST');
assert(blocked.status === 409, `erase blocked by the hold → 409 (got ${blocked.status})`);
assert((await ticketSubject(A, ticket.id)) === PII, 'PII still present while held');

// Release the hold → erasure now redacts the PII in place.
await json(A, `/v1/dsr/holds/${hold.id}/release`, 'POST');
const erased = await json(A, '/v1/dsr/erase', 'POST');
assert(erased.status === 'ERASED', `erase succeeds after release (status ${erased.status})`);
assert((await ticketSubject(A, ticket.id)) === '[redacted]', 'ticket subject is redacted (erased in place, not deleted)');

// Idempotent: a second erase is a no-op.
const again = await json(A, '/v1/dsr/erase', 'POST');
assert(again.status === 'ALREADY_ERASED', `re-erase is idempotent (status ${again.status})`);

// Tenant fence: tenant B never sees A's holds.
assert(!(await json(B, '/v1/dsr/holds')).some((h) => h.id === hold.id), "tenant B's hold list excludes A's hold");

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