// Journey: TOTP full lifecycle (enroll → verify → backup-codes → disable)
//
// Endpoints exercised:
//   GET    /api/v1/members/me/totp                  (before)
//   POST   /api/v1/members/me/totp/enroll-start
//   POST   /api/v1/members/me/totp/enroll-confirm
//   POST   /api/v1/members/me/totp/verify
//   POST   /api/v1/members/me/totp/backup-codes     (regenerate)
//   GET    /api/v1/members/me/totp                  (mid; enrolled=true)
//   DELETE /api/v1/members/me/totp
//   GET    /api/v1/members/me/totp                  (after; enrolled=false)
//
// TOTP code generation is local (lib/totp.js) — no external authenticator.
import { sleep } from 'k6';
import { get, post, del, checkEnvelope } from '../lib/http.js';
import { registerAndConfirm } from '../lib/auth.js';
import { generateTOTP, parseOTPAuth } from '../lib/totp.js';

export const options = {
  vus: 1,
  iterations: 1,
  thresholds: { checks: ['rate==1.0'] },
};

export default function () {
  const { tokens } = registerAndConfirm();
  const bearer = { Authorization: `Bearer ${tokens.access_token}` };

  // 1. status before enroll
  const before = checkEnvelope(get('/api/v1/members/me/totp', bearer), 'GET /me/totp (before)').data;
  if (before.enrolled !== false) throw new Error('totp should be disabled initially');

  // 2. enroll-start → get otpauth URL
  const enroll = checkEnvelope(post('/api/v1/members/me/totp/enroll-start', null, bearer), 'POST /me/totp/enroll-start').data;
  parseOTPAuth(enroll.otpauth_url); // validate it parses
  const code = generateTOTP(enroll.otpauth_url);

  // 3. enroll-confirm
  const confirm = checkEnvelope(
    post('/api/v1/members/me/totp/enroll-confirm', { code }, bearer),
    'POST /me/totp/enroll-confirm',
  ).data;
  if (!Array.isArray(confirm.backup_codes) || confirm.backup_codes.length === 0) {
    throw new Error('enroll-confirm: backup_codes missing');
  }

  // 4. status mid (enrolled=true)
  const mid = checkEnvelope(get('/api/v1/members/me/totp', bearer), 'GET /me/totp (mid)').data;
  if (mid.enrolled !== true) throw new Error('totp should be enrolled after confirm');

  // 5. verify — wait until current TOTP differs from the one just consumed
  //   so the replay guard (Member.TOTP.ReplayTTLSeconds) does not reject it.
  let verifyCode = generateTOTP(enroll.otpauth_url);
  for (let i = 0; verifyCode === code && i < 30; i++) {
    sleep(1.1);
    verifyCode = generateTOTP(enroll.otpauth_url);
  }
  if (verifyCode === code) {
    throw new Error('TOTP window did not advance; cannot avoid replay');
  }
  checkEnvelope(
    post('/api/v1/members/me/totp/verify', { code: verifyCode }, bearer),
    'POST /me/totp/verify',
  );

  // 6. backup-codes (regenerate)
  const regen = checkEnvelope(
    post('/api/v1/members/me/totp/backup-codes', null, bearer),
    'POST /me/totp/backup-codes',
  ).data;
  if (!Array.isArray(regen.backup_codes) || regen.backup_codes.length === 0) {
    throw new Error('backup-codes: missing codes');
  }

  // 7. disable
  checkEnvelope(del('/api/v1/members/me/totp', null, bearer), 'DELETE /me/totp');

  // 8. status after disable
  const after = checkEnvelope(get('/api/v1/members/me/totp', bearer), 'GET /me/totp (after)').data;
  if (after.enrolled !== false) throw new Error('totp should be disabled after DELETE');
}
