// Journey: admin RBAC end-to-end (login admin → roles CRUD → assign → reload → verify → cleanup)
//
// Endpoints exercised (happy path, admin role required):
//   POST   /api/v1/auth/login                          (admin)
//   GET    /api/v1/permissions/roles
//   POST   /api/v1/permissions/roles
//   PATCH  /api/v1/permissions/roles/:id
//   GET    /api/v1/permissions/roles/:id/permissions
//   PUT    /api/v1/permissions/roles/:id/permissions
//   GET    /api/v1/permissions/catalog                 (to pick a permission_id)
//   POST   /api/v1/permissions/users/:uid/roles
//   GET    /api/v1/permissions/users/:uid/roles
//   POST   /api/v1/permissions/policy/reload
//   GET    /api/v1/permissions/me                      (as the assigned user)
//   DELETE /api/v1/permissions/users/:uid/roles/:role_id
//   DELETE /api/v1/permissions/roles/:id
//
// PREREQUISITE — admin user seeded by `make k6-seed-admin`. ADMIN_EMAIL,
// ADMIN_PASSWORD env vars must be set (k6.env exports them). If unset the
// journey prints a skip notice and exits 0 (covered by smoke/permission_admin
// for route-existence checks).
import { sleep } from 'k6';
import { get, post, put, patch, del, checkEnvelope } from '../lib/http.js';
import { login } from '../lib/auth.js';
import { registerAndConfirm } from '../lib/auth.js';
import { cfg, unique } from '../lib/config.js';

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

function pickAnyPermissionID(bearer) {
  const cat = checkEnvelope(get('/api/v1/permissions/catalog', bearer), 'GET /permissions/catalog').data;
  const list = (cat && (cat.list || cat.tree)) || [];
  // Find a leaf — node with http_methods set.
  const stack = [...list];
  while (stack.length) {
    const n = stack.shift();
    if (n.http_methods && n.id) return n.id;
    if (n.children) for (const c of n.children) stack.push(c);
  }
  if (list.length > 0 && list[0].id) return list[0].id;
  throw new Error('catalog: no permissions found');
}

export default function () {
  if (!cfg.adminEmail || (!cfg.adminPassword && !cfg.adminAccessToken)) {
    console.log('[rbac_admin] skipped: ADMIN_EMAIL + (ADMIN_PASSWORD or ADMIN_ACCESS_TOKEN) env not set. Run `make k6-seed-admin` first.');
    return;
  }

  // 1. obtain admin access_token. Prefer ADMIN_ACCESS_TOKEN (issued by
  // k6-seed-admin at registration time) because ZITADEL v2 disables OAuth
  // password grant by default, so /auth/login → VerifyPassword returns 502.
  let adminAccessToken = cfg.adminAccessToken;
  if (!adminAccessToken) {
    const admin = login({ email: cfg.adminEmail, password: cfg.adminPassword });
    adminAccessToken = admin.access_token;
  }
  const adminBearer = { Authorization: `Bearer ${adminAccessToken}` };

  // 2. register a target user (we'll assign a role to them and verify via /me)
  const { tokens: target } = registerAndConfirm();
  const targetBearer = { Authorization: `Bearer ${target.access_token}` };

  // 3. list roles (sanity)
  const rolesBefore = checkEnvelope(get('/api/v1/permissions/roles', adminBearer), 'GET /roles').data;
  if (!rolesBefore || !Array.isArray(rolesBefore.roles)) throw new Error('roles list bad shape');

  // 4. create a role
  const roleKey = unique('k6role').replace(/[^a-z0-9_]/g, '_');
  const created = checkEnvelope(
    post('/api/v1/permissions/roles', { key: roleKey, display_name: 'k6 RBAC role' }, adminBearer),
    'POST /roles',
  ).data;
  const roleID = created.id;

  // 5. patch role display name
  const newDisplay = `${created.display_name} (patched)`;
  checkEnvelope(
    patch(`/api/v1/permissions/roles/${roleID}`, { display_name: newDisplay }, adminBearer),
    'PATCH /roles/:id',
  );

  // 6. get role permissions (empty initially)
  checkEnvelope(get(`/api/v1/permissions/roles/${roleID}/permissions`, adminBearer), 'GET /roles/:id/permissions');

  // 7. put role permissions (replace with one permission id picked from catalog)
  const permID = pickAnyPermissionID(adminBearer);
  checkEnvelope(
    put(`/api/v1/permissions/roles/${roleID}/permissions`, { permission_ids: [permID] }, adminBearer),
    'PUT /roles/:id/permissions',
  );

  // 8. assign role to target user
  checkEnvelope(
    post(`/api/v1/permissions/users/${target.uid}/roles`, { role_id: roleID }, adminBearer),
    'POST /users/:uid/roles',
  );

  // 9. list target user's roles
  const targetRoles = checkEnvelope(
    get(`/api/v1/permissions/users/${target.uid}/roles`, adminBearer),
    'GET /users/:uid/roles',
  ).data;
  const hasRole = targetRoles.user_roles && targetRoles.user_roles.some((ur) => ur.role_id === roleID);
  if (!hasRole) throw new Error('assigned role not visible in user_roles');

  // 10. policy reload
  checkEnvelope(post('/api/v1/permissions/policy/reload', {}, adminBearer), 'POST /policy/reload');

  // give the gateway a moment for the pub/sub reload to settle
  sleep(0.5);

  // 11. target /permissions/me reflects the role
  const me = checkEnvelope(get('/api/v1/permissions/me', targetBearer), 'GET /permissions/me').data;
  if (!me.roles || !me.roles.includes(roleKey)) {
    throw new Error(`/permissions/me did not include role ${roleKey}: got=${JSON.stringify(me.roles)}`);
  }

  // 12. revoke role
  checkEnvelope(
    del(`/api/v1/permissions/users/${target.uid}/roles/${roleID}`, null, adminBearer),
    'DELETE /users/:uid/roles/:role_id',
  );

  // 13. delete role (now safe because no user is assigned)
  checkEnvelope(del(`/api/v1/permissions/roles/${roleID}`, null, adminBearer), 'DELETE /roles/:id');
}
