import { createPasswordReset } from '../../auth/reset.js';
import { createInvite, listInvites, resendInvite, cancelInvite, updateInviteRole, extendInvite, } from '../../auth/invites.js';
import { listTeamMembers, getUserStats, computeAccessReview } from '../../auth/team.js';
import { getUserDetail, listUserSessions, listUserActivity, getUserPermissionView, transferContentOwnership, } from '../../auth/user-admin.js';
import { normalizeAssignableRole, roleRank, isOwnerRole } from '../../auth/roles.js';
import { logEvent } from '../../security/audit.js';
import { getActuateConfig } from '../../config/runtime.js';
import { generateApiKey } from '../../security/api-key-enhanced.js';
import { guardedDb } from '../guarded-db.js';
import { json, errorResponse, internalError, hasModel, modelNotAvailable, requirePasswordReauth, requireAuth, requireAdminScope, ADMIN_ROLES, requireRole, teamOptionsFor, isEmailConfiguredAsync, inviteLimiter, checkRateLimitAsync, clientIp, } from '../route-helpers.js';
export function registerUserRoutes(router) {
    const db = guardedDb;
    // ---------------------------------------------------------------------------
    // API key management (admin-only). Keys are created here, hashed in the DB,
    // and shown to the caller exactly once. They authenticate programmatic
    // clients (AI agents, CI, integrations) against the same REST surface as a
    // user session, but skip CSRF and use scope-based authorization.
    // ---------------------------------------------------------------------------
    router.get('/api-keys', async (request) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const adminErr = requireAdminScope(auth.session);
            if (adminErr)
                return adminErr;
            const d = db();
            if (!hasModel(d, 'apiKey'))
                return json({ data: [] });
            const keys = await d.apiKey.findMany({
                orderBy: { createdAt: 'desc' },
                select: {
                    id: true,
                    name: true,
                    keyPrefix: true,
                    scopes: true,
                    ipRestrictions: true,
                    expiresAt: true,
                    lastUsedAt: true,
                    revokedAt: true,
                    createdAt: true,
                    user: { select: { id: true, name: true, email: true } },
                },
            });
            return json({ data: keys });
        }
        catch (err) {
            return internalError(err, 'api-keys/list');
        }
    });
    router.post('/api-keys', async (request) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const adminErr = requireAdminScope(auth.session);
            if (adminErr)
                return adminErr;
            // Issuing a new credential is a sensitive action — require reauth for
            // session-authenticated callers. API-key-authenticated requests are
            // already proving possession of a long-lived credential.
            if (!auth.session.apiKey) {
                const reauthErr = await requirePasswordReauth(request, auth.session.userId);
                if (reauthErr)
                    return reauthErr;
            }
            const body = (await request.json());
            if (!body.name || typeof body.name !== 'string' || body.name.trim().length === 0) {
                return errorResponse('name is required', 400);
            }
            if (body.name.length > 100) {
                return errorResponse('name must be 100 characters or fewer', 400);
            }
            const scopes = body.scopes ?? {};
            // Sanity check the scope shape so we don't store garbage that fails
            // every authorization check at runtime.
            if (!scopes.admin &&
                !scopes.media &&
                !scopes.pageBuilder &&
                (!scopes.collections || scopes.collections.length === 0) &&
                (!scopes.globals || scopes.globals.length === 0)) {
                return errorResponse('scopes must grant at least one capability (admin, media, pageBuilder, collections, or globals)', 400);
            }
            let expiresAt;
            if (body.expiresAt) {
                const parsed = new Date(body.expiresAt);
                if (isNaN(parsed.getTime()))
                    return errorResponse('Invalid expiresAt date', 400);
                if (parsed.getTime() < Date.now())
                    return errorResponse('expiresAt must be in the future', 400);
                expiresAt = parsed;
            }
            const { key, keyHash, keyPrefix } = await generateApiKey({
                prefix: 'act_sk',
                scopes,
                expiresAt,
            });
            const d = db();
            const record = await d.apiKey.create({
                data: {
                    name: body.name.trim(),
                    keyHash,
                    keyPrefix,
                    userId: auth.session.userId,
                    scopes: scopes,
                    ipRestrictions: body.ipRestrictions?.length ? body.ipRestrictions : null,
                    expiresAt: expiresAt ?? null,
                },
                select: {
                    id: true,
                    name: true,
                    keyPrefix: true,
                    scopes: true,
                    ipRestrictions: true,
                    expiresAt: true,
                    createdAt: true,
                },
            });
            await logEvent({
                event: 'api_key_created',
                userId: auth.session.userId,
                ipAddress: clientIp(request),
                userAgent: request.headers.get('user-agent') ?? undefined,
                details: { apiKeyId: record.id, name: record.name, scopes },
            });
            // The raw `key` is the only thing the caller will ever see; we never
            // store it in plaintext. Document this in the response shape.
            return json({ data: { ...record, key } }, 201);
        }
        catch (err) {
            return internalError(err, 'api-keys/create');
        }
    });
    router.delete('/api-keys/:id', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const adminErr = requireAdminScope(auth.session);
            if (adminErr)
                return adminErr;
            // Same reauth gate as creation — revocation is irreversible and
            // affects every dependent client.
            if (!auth.session.apiKey) {
                const reauthErr = await requirePasswordReauth(request, auth.session.userId);
                if (reauthErr)
                    return reauthErr;
            }
            const d = db();
            const existing = await d.apiKey.findUnique({ where: { id: params.id } });
            if (!existing)
                return errorResponse('API key not found', 404);
            await d.apiKey.update({
                where: { id: params.id },
                data: { revokedAt: new Date() },
            });
            await logEvent({
                event: 'api_key_revoked',
                userId: auth.session.userId,
                ipAddress: clientIp(request),
                userAgent: request.headers.get('user-agent') ?? undefined,
                details: { apiKeyId: existing.id, name: existing.name },
            });
            return json({ data: { revoked: true } });
        }
        catch (err) {
            return internalError(err, 'api-keys/revoke');
        }
    });
    // ---------------------------------------------------------------------------
    // Users route
    // ---------------------------------------------------------------------------
    router.get('/users', async (request) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'user'))
                return modelNotAvailable('user');
            const url = new URL(request.url);
            const members = await listTeamMembers(d, {
                search: url.searchParams.get('search') ?? undefined,
                role: url.searchParams.get('role') ?? undefined,
                status: url.searchParams.get('status') ?? undefined,
            }, teamOptionsFor(auth.session));
            return json({ data: members });
        }
        catch (err) {
            return internalError(err);
        }
    });
    router.get('/users/stats', async (request) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'user'))
                return modelNotAvailable('user');
            const stats = await getUserStats(d, teamOptionsFor(auth.session));
            return json({ data: { ...stats, emailConfigured: await isEmailConfiguredAsync(d) } });
        }
        catch (err) {
            return internalError(err, 'user stats');
        }
    });
    // Lightweight directory lookup used by the comment mention picker.
    // Any authenticated user can query this — mentions are a
    // collaboration primitive, not an admin tool. Returns ONLY the
    // minimal subset needed to render a picker row (id, name, email);
    // never expose role, hashed-password, or audit metadata here.
    //
    // The matcher is intentionally simple: case-insensitive `contains`
    // against name + email, capped at 10 results. The picker UI is a
    // dropdown — anything beyond 10 rows is unusable and would only
    // serve as a directory-enumeration attack vector. Inactive /
    // unapproved users are filtered out so the picker can't show
    // someone the actor can't actually collaborate with (the mention
    // would create a comment_mention notification that no one ever
    // reads).
    router.get('/users/search', async (request) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const url = new URL(request.url);
            const qRaw = (url.searchParams.get('q') ?? '').trim();
            // Hard floor on query length: avoids dumping the entire user
            // table when a UI sends an empty `q` by mistake, and avoids
            // making the endpoint a generic directory browser.
            if (qRaw.length === 0) {
                return json({ data: [] });
            }
            // Hard ceiling on query length: 64 chars is more than any name
            // or email prefix needs and prevents pathological regex / LIKE
            // workloads from a hostile client.
            const q = qRaw.slice(0, 64);
            // Mention only people the actor can actually collaborate
            // with — inactive accounts can't be reached, and unapproved
            // accounts haven't passed admin gating yet. Either user would
            // produce a `comment_mention` notification that goes nowhere
            // (PR #159 Copilot finding — the previous query only filtered
            // `isActive` even though the route comment promised both).
            const users = await db().user.findMany({
                where: {
                    isActive: true,
                    isApproved: true,
                    OR: [
                        { name: { contains: q, mode: 'insensitive' } },
                        { email: { contains: q, mode: 'insensitive' } },
                    ],
                },
                select: { id: true, name: true, email: true },
                orderBy: { name: 'asc' },
                take: 10,
            });
            return json({ data: users });
        }
        catch (err) {
            return internalError(err, 'users search');
        }
    });
    router.post('/users/invite', async (request) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            if (!(await checkRateLimitAsync(inviteLimiter, `invite:${auth.session.userId}`))) {
                return errorResponse('Too many invitations. Please try again later.', 429);
            }
            const d = db();
            if (!hasModel(d, 'user'))
                return modelNotAvailable('user');
            if (!hasModel(d, 'invite'))
                return modelNotAvailable('invite');
            const body = (await request.json());
            if (!body.email)
                return errorResponse('Email is required', 400);
            // Only Owners may invite Owners.
            if ((body.role ?? '').toUpperCase() === 'OWNER' && auth.session.role !== 'OWNER') {
                return errorResponse('Only an Owner can assign the Owner role.', 403);
            }
            const opts = teamOptionsFor(auth.session);
            // Seat enforcement: active members + pending invites reserve seats.
            const stats = await getUserStats(d, opts);
            if (stats.seatsAvailable != null && stats.seatsAvailable <= 0) {
                return errorResponse(`All ${stats.seatLimit} seats are in use. Remove a member or raise the seat limit before inviting.`, 409);
            }
            const cmsConfig = getActuateConfig();
            const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? new URL(request.url).origin;
            const inviter = await d.user.findUnique({
                where: { id: auth.session.userId },
                select: { name: true },
            });
            const result = await createInvite(d, {
                email: body.email,
                role: body.role ?? 'VIEWER',
                message: body.message,
                expiresInDays: body.expiresInDays,
            }, {
                siteUrl,
                // Invite accept links land on the reset screen, which lives on the
                // login mount when split login is configured.
                adminPath: cmsConfig?.admin?.loginPath ?? cmsConfig?.admin?.path ?? '/admin',
                invitedByName: inviter?.name ?? undefined,
                invitedByUserId: auth.session.userId,
                siteName: cmsConfig?.admin?.branding?.name ??
                    undefined,
            });
            if (!result.success) {
                return errorResponse(result.error ?? 'Could not create invitation', 400);
            }
            await logEvent({
                event: 'user_invited',
                userId: auth.session.userId,
                details: {
                    targetEmail: result.invite?.email,
                    role: result.invite?.role,
                    inviteId: result.invite?.id,
                    emailed: result.emailed,
                },
            });
            return json({
                data: {
                    success: true,
                    invite: result.invite,
                    emailed: result.emailed,
                    inviteUrl: result.inviteUrl,
                },
            });
        }
        catch (err) {
            return internalError(err, 'user invite');
        }
    });
    router.delete('/users/:id', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            if (auth.session.userId === params.id) {
                return errorResponse('Cannot revoke your own access', 400);
            }
            const user = await db().user.findUnique({ where: { id: params.id } });
            if (!user)
                return errorResponse('User not found', 404);
            // Last-Owner protection: never deactivate the only remaining active Owner.
            if (isOwnerRole(user.role)) {
                if (auth.session.role !== 'OWNER') {
                    return errorResponse('Only an Owner can revoke another Owner.', 403);
                }
                const otherOwners = await db().user.count({
                    where: { role: 'OWNER', isActive: true, id: { not: params.id } },
                });
                if (otherOwners === 0) {
                    return errorResponse('Cannot revoke access for the last Owner.', 409);
                }
            }
            await db().user.update({
                where: { id: params.id },
                data: { isActive: false, suspendedAt: new Date() },
            });
            await db().session.updateMany({
                where: { userId: params.id },
                data: { revokedAt: new Date() },
            });
            await logEvent({
                event: 'user_deactivated',
                userId: auth.session.userId,
                details: { targetUserId: params.id, targetEmail: user.email },
            });
            return json({ data: { success: true } });
        }
        catch (err) {
            return internalError(err);
        }
    });
    // Change a user's role, with Owner / last-Owner / self-escalation protections.
    router.patch('/users/:id/role', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'user'))
                return modelNotAvailable('user');
            const body = (await request.json());
            const newRole = normalizeAssignableRole(body.role);
            if (!newRole)
                return errorResponse('Select a valid role.', 400);
            const target = await d.user.findUnique({ where: { id: params.id } });
            if (!target)
                return errorResponse('User not found', 404);
            const targetIsOwner = isOwnerRole(target.role);
            const promotingToOwner = newRole === 'OWNER';
            // Only an Owner may grant or remove the Owner role.
            if ((promotingToOwner || targetIsOwner) && auth.session.role !== 'OWNER') {
                return errorResponse('Only an Owner can assign or change the Owner role.', 403);
            }
            // No self-escalation.
            if (auth.session.userId === target.id && roleRank(newRole) > roleRank(auth.session.role)) {
                return errorResponse('You cannot raise your own role.', 403);
            }
            // Last-Owner protection: don't demote the only remaining active Owner.
            if (targetIsOwner && !promotingToOwner) {
                const otherOwners = await d.user.count({
                    where: { role: 'OWNER', isActive: true, id: { not: target.id } },
                });
                if (otherOwners === 0) {
                    return errorResponse('Cannot change the role of the last Owner.', 409);
                }
            }
            if (target.role === newRole) {
                return json({ data: { success: true, role: newRole } });
            }
            const downgrade = roleRank(newRole) < roleRank(target.role);
            await d.user.update({ where: { id: target.id }, data: { role: newRole } });
            // Revoke sessions on downgrade so reduced permissions take effect at once.
            if (downgrade) {
                await d.session.updateMany({
                    where: { userId: target.id, revokedAt: null },
                    data: { revokedAt: new Date() },
                });
            }
            await logEvent({
                event: 'user_role_changed',
                userId: auth.session.userId,
                details: {
                    targetUserId: target.id,
                    targetEmail: target.email,
                    previousRole: target.role,
                    newRole,
                    sessionsRevoked: downgrade,
                },
            });
            return json({ data: { success: true, role: newRole, sessionsRevoked: downgrade } });
        }
        catch (err) {
            return internalError(err, 'user role change');
        }
    });
    // ---------------------------------------------------------------------------
    // User detail drawer routes (overview / security / sessions / activity /
    // permissions) + per-user security actions.
    // ---------------------------------------------------------------------------
    /**
     * Load the target user for a security action and run the guards common to all
     * of them. Returns either the user row or an error Response. `requireOwner`
     * is set true for actions where acting on an Owner is owner-only; `blockSelf`
     * for actions a user must not perform on their own account.
     */
    async function loadActionTarget(auth, id, { blockSelf = false } = {}) {
        const d = db();
        if (!hasModel(d, 'user'))
            return { error: modelNotAvailable('user') };
        const user = await d.user.findUnique({ where: { id } });
        if (!user)
            return { error: errorResponse('User not found', 404) };
        if (blockSelf && auth.session.userId === id) {
            return { error: errorResponse('You cannot perform this action on your own account.', 400) };
        }
        // Acting on an Owner is Owner-only.
        if (isOwnerRole(user.role) && auth.session.role !== 'OWNER') {
            return { error: errorResponse('Only an Owner can manage another Owner.', 403) };
        }
        return { user };
    }
    /** Whether deactivating/suspending/locking `userId` would remove the last Owner. */
    async function wouldOrphanOwnership(user, userId) {
        if (!isOwnerRole(user.role))
            return false;
        const otherOwners = await db().user.count({
            where: { role: 'OWNER', isActive: true, id: { not: userId } },
        });
        return otherOwners === 0;
    }
    router.get('/users/:id', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'user'))
                return modelNotAvailable('user');
            const opts = teamOptionsFor(auth.session);
            const detail = await getUserDetail(d, params.id, {
                workspaceDomain: opts.workspaceDomain,
                currentUserId: auth.session.userId,
            });
            if (!detail)
                return errorResponse('User not found', 404);
            return json({ data: detail });
        }
        catch (err) {
            return internalError(err, 'user detail');
        }
    });
    router.get('/users/:id/sessions', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'session'))
                return modelNotAvailable('session');
            // Mark "this device" only when an admin views their own sessions.
            const currentSessionId = auth.session.userId === params.id ? auth.session.sessionId : undefined;
            const sessions = await listUserSessions(d, params.id, currentSessionId);
            return json({ data: sessions });
        }
        catch (err) {
            return internalError(err, 'user sessions');
        }
    });
    router.delete('/users/:id/sessions/:sessionId', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'session'))
                return modelNotAvailable('session');
            const target = await loadActionTarget(auth, params.id);
            if (target.error)
                return target.error;
            const session = await d.session.findUnique({ where: { id: params.sessionId } });
            if (!session || session.userId !== params.id) {
                return errorResponse('Session not found', 404);
            }
            await d.session.update({
                where: { id: params.sessionId },
                data: { revokedAt: new Date() },
            });
            await logEvent({
                event: 'session_revoked',
                userId: auth.session.userId,
                ipAddress: clientIp(request),
                userAgent: request.headers.get('user-agent') ?? undefined,
                details: { targetUserId: params.id, sessionId: params.sessionId },
            });
            return json({ data: { success: true } });
        }
        catch (err) {
            return internalError(err, 'revoke session');
        }
    });
    router.post('/users/:id/sessions/revoke-all', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'session'))
                return modelNotAvailable('session');
            const target = await loadActionTarget(auth, params.id);
            if (target.error)
                return target.error;
            // When an admin revokes their own sessions, keep the current one so the
            // action doesn't log them out mid-flow (the UI offers a separate path for
            // that). For any other user, revoke everything.
            const keepCurrent = auth.session.userId === params.id;
            await d.session.updateMany({
                where: {
                    userId: params.id,
                    revokedAt: null,
                    ...(keepCurrent ? { id: { not: auth.session.sessionId } } : {}),
                },
                data: { revokedAt: new Date() },
            });
            await logEvent({
                event: 'sessions_revoked_all',
                userId: auth.session.userId,
                ipAddress: clientIp(request),
                userAgent: request.headers.get('user-agent') ?? undefined,
                details: { targetUserId: params.id, keptCurrent: keepCurrent },
            });
            return json({ data: { success: true } });
        }
        catch (err) {
            return internalError(err, 'revoke all sessions');
        }
    });
    router.get('/users/:id/activity', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'auditLog'))
                return json({ data: [] });
            const url = new URL(request.url);
            const limit = Number(url.searchParams.get('limit') ?? '50');
            const events = await listUserActivity(d, params.id, Number.isFinite(limit) ? limit : 50);
            return json({ data: events });
        }
        catch (err) {
            return internalError(err, 'user activity');
        }
    });
    router.get('/users/:id/permissions', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'user'))
                return modelNotAvailable('user');
            const user = await d.user.findUnique({
                where: { id: params.id },
                select: { role: true },
            });
            if (!user)
                return errorResponse('User not found', 404);
            return json({ data: getUserPermissionView(user.role) });
        }
        catch (err) {
            return internalError(err, 'user permissions');
        }
    });
    // Send a password reset email to the user. Admins never set or see passwords;
    // this triggers the standard single-use, expiring, hashed-token reset flow.
    router.post('/users/:id/password-reset', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const target = await loadActionTarget(auth, params.id);
            if (target.error)
                return target.error;
            const user = target.user;
            // SSO/OAuth-only accounts have no local password to reset.
            if (!user.passwordHash) {
                return errorResponse('This account signs in with an external identity provider and has no password to reset.', 409);
            }
            // Resetting an Admin/Owner password is high-impact — require reauth.
            if (isOwnerRole(user.role) || String(user.role).toUpperCase() === 'ADMIN') {
                const reauthErr = await requirePasswordReauth(request, auth.session.userId);
                if (reauthErr)
                    return reauthErr;
            }
            if (!(await isEmailConfiguredAsync(db()))) {
                return errorResponse('Email is not configured, so a reset link cannot be sent. Configure an email provider first.', 409);
            }
            const cmsConfig = getActuateConfig();
            const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? new URL(request.url).origin;
            await createPasswordReset(db(), user.email, {
                siteUrl,
                // Split-login installs serve the reset screen on the login mount.
                adminPath: cmsConfig?.admin?.loginPath ?? cmsConfig?.admin?.path ?? '/admin',
                platform: cmsConfig?.platform,
            });
            await logEvent({
                event: 'password_reset_sent',
                userId: auth.session.userId,
                ipAddress: clientIp(request),
                userAgent: request.headers.get('user-agent') ?? undefined,
                details: { targetUserId: user.id, targetEmail: user.email },
            });
            return json({ data: { success: true, emailed: true } });
        }
        catch (err) {
            return internalError(err, 'admin password reset');
        }
    });
    // Toggle "must reset password on next login". Body: { enabled: boolean }.
    router.post('/users/:id/force-password-reset', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const target = await loadActionTarget(auth, params.id);
            if (target.error)
                return target.error;
            const body = (await request.json().catch(() => ({})));
            const enabled = body.enabled !== false;
            await db().user.update({
                where: { id: params.id },
                data: { forcePasswordReset: enabled },
            });
            await logEvent({
                event: enabled ? 'force_password_reset_enabled' : 'force_password_reset_cleared',
                userId: auth.session.userId,
                details: { targetUserId: params.id },
            });
            return json({ data: { success: true, forcePasswordReset: enabled } });
        }
        catch (err) {
            return internalError(err, 'force password reset');
        }
    });
    // Reset MFA enrollment: clears the TOTP secret + backup codes and revokes the
    // user's sessions. High-impact — requires reauth and protects the last Owner.
    router.post('/users/:id/mfa-reset', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const target = await loadActionTarget(auth, params.id);
            if (target.error)
                return target.error;
            const user = target.user;
            const reauthErr = await requirePasswordReauth(request, auth.session.userId);
            if (reauthErr)
                return reauthErr;
            // Don't strip the last Owner's MFA — that would leave the most privileged
            // account with the weakest protection and no recovery-safe path.
            if (isOwnerRole(user.role) && (await wouldOrphanOwnership(user, user.id))) {
                return errorResponse('Cannot reset MFA for the last Owner. Add another Owner first.', 409);
            }
            const d = db();
            await d.user.update({
                where: { id: user.id },
                data: { totpEnabled: false, totpSecret: null, backupCodes: null },
            });
            if (hasModel(d, 'session')) {
                await d.session.updateMany({
                    where: { userId: user.id, revokedAt: null },
                    data: { revokedAt: new Date() },
                });
            }
            await logEvent({
                event: 'mfa_reset',
                userId: auth.session.userId,
                ipAddress: clientIp(request),
                userAgent: request.headers.get('user-agent') ?? undefined,
                details: { targetUserId: user.id, targetEmail: user.email },
            });
            return json({ data: { success: true } });
        }
        catch (err) {
            return internalError(err, 'mfa reset');
        }
    });
    // Require the user to enrol MFA before next access. Body: { required: boolean }.
    router.post('/users/:id/require-mfa', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const target = await loadActionTarget(auth, params.id);
            if (target.error)
                return target.error;
            const body = (await request.json().catch(() => ({})));
            const required = body.required !== false;
            await db().user.update({
                where: { id: params.id },
                data: { mfaRequired: required },
            });
            await logEvent({
                event: required ? 'mfa_required' : 'mfa_requirement_cleared',
                userId: auth.session.userId,
                details: { targetUserId: params.id },
            });
            return json({ data: { success: true, mfaRequired: required } });
        }
        catch (err) {
            return internalError(err, 'require mfa');
        }
    });
    router.post('/users/:id/suspend', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const target = await loadActionTarget(auth, params.id, { blockSelf: true });
            if (target.error)
                return target.error;
            if (await wouldOrphanOwnership(target.user, params.id)) {
                return errorResponse('Cannot suspend the last Owner.', 409);
            }
            const d = db();
            await d.user.update({ where: { id: params.id }, data: { suspendedAt: new Date() } });
            if (hasModel(d, 'session')) {
                await d.session.updateMany({
                    where: { userId: params.id, revokedAt: null },
                    data: { revokedAt: new Date() },
                });
            }
            await logEvent({
                event: 'user_suspended',
                userId: auth.session.userId,
                details: { targetUserId: params.id, targetEmail: target.user.email },
            });
            return json({ data: { success: true } });
        }
        catch (err) {
            return internalError(err, 'suspend user');
        }
    });
    router.post('/users/:id/reactivate', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const target = await loadActionTarget(auth, params.id);
            if (target.error)
                return target.error;
            // Reactivate restores access from any non-active lifecycle state.
            await db().user.update({
                where: { id: params.id },
                data: { suspendedAt: null, lockedAt: null, isActive: true, failedLoginCount: 0 },
            });
            await logEvent({
                event: 'user_reactivated',
                userId: auth.session.userId,
                details: { targetUserId: params.id, targetEmail: target.user.email },
            });
            return json({ data: { success: true } });
        }
        catch (err) {
            return internalError(err, 'reactivate user');
        }
    });
    router.post('/users/:id/lock', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const target = await loadActionTarget(auth, params.id, { blockSelf: true });
            if (target.error)
                return target.error;
            if (await wouldOrphanOwnership(target.user, params.id)) {
                return errorResponse('Cannot lock the last Owner.', 409);
            }
            const d = db();
            await d.user.update({ where: { id: params.id }, data: { lockedAt: new Date() } });
            if (hasModel(d, 'session')) {
                await d.session.updateMany({
                    where: { userId: params.id, revokedAt: null },
                    data: { revokedAt: new Date() },
                });
            }
            await logEvent({
                event: 'user_locked',
                userId: auth.session.userId,
                details: { targetUserId: params.id, targetEmail: target.user.email },
            });
            return json({ data: { success: true } });
        }
        catch (err) {
            return internalError(err, 'lock user');
        }
    });
    router.post('/users/:id/unlock', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const target = await loadActionTarget(auth, params.id);
            if (target.error)
                return target.error;
            await db().user.update({
                where: { id: params.id },
                data: { lockedAt: null, failedLoginCount: 0 },
            });
            await logEvent({
                event: 'user_unlocked',
                userId: auth.session.userId,
                details: { targetUserId: params.id, targetEmail: target.user.email },
            });
            return json({ data: { success: true } });
        }
        catch (err) {
            return internalError(err, 'unlock user');
        }
    });
    // Reassign all content owned by :id to another active user. Used before
    // removing someone so their documents/media/templates survive with a real
    // owner. Body: { toUserId }.
    router.post('/users/:id/transfer-content', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const target = await loadActionTarget(auth, params.id);
            if (target.error)
                return target.error;
            const body = (await request.json().catch(() => ({})));
            const toUserId = body.toUserId;
            if (!toUserId)
                return errorResponse('A recipient (toUserId) is required.', 400);
            if (toUserId === params.id) {
                return errorResponse('Choose a different user to receive the content.', 400);
            }
            const recipient = await db().user.findUnique({ where: { id: toUserId } });
            if (!recipient)
                return errorResponse('Recipient not found.', 404);
            if (recipient.isActive === false || recipient.suspendedAt || recipient.lockedAt) {
                return errorResponse('Content can only be transferred to an active user.', 409);
            }
            const counts = await transferContentOwnership(db(), params.id, toUserId);
            await logEvent({
                event: 'content_ownership_transferred',
                userId: auth.session.userId,
                details: {
                    fromUserId: params.id,
                    toUserId,
                    documents: counts.documents,
                    media: counts.media,
                    templates: counts.templates,
                    total: counts.total,
                },
            });
            return json({ data: { success: true, ...counts } });
        }
        catch (err) {
            return internalError(err, 'transfer content ownership');
        }
    });
    // ---------------------------------------------------------------------------
    // Invite management routes
    // ---------------------------------------------------------------------------
    router.get('/invites', async (request) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'invite'))
                return modelNotAvailable('invite');
            const url = new URL(request.url);
            const invites = await listInvites(d, {
                status: url.searchParams.get('status') ?? undefined,
                search: url.searchParams.get('search') ?? undefined,
            });
            return json({ data: invites });
        }
        catch (err) {
            return internalError(err, 'list invites');
        }
    });
    function inviteConfigFor(request, inviterName) {
        const cfg = getActuateConfig();
        return {
            siteUrl: process.env.NEXT_PUBLIC_SITE_URL ?? new URL(request.url).origin,
            adminPath: cfg?.admin?.loginPath ?? cfg?.admin?.path ?? '/admin',
            invitedByName: inviterName,
            siteName: cfg?.admin?.branding?.name ?? undefined,
        };
    }
    router.post('/invites/:id/resend', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            if (!(await checkRateLimitAsync(inviteLimiter, `invite:${auth.session.userId}`))) {
                return errorResponse('Too many invitations. Please try again later.', 429);
            }
            const d = db();
            if (!hasModel(d, 'invite'))
                return modelNotAvailable('invite');
            const result = await resendInvite(d, params.id, inviteConfigFor(request));
            if (!result.success) {
                return errorResponse(result.error ?? 'Could not resend invitation', 400);
            }
            await logEvent({
                event: 'invite_resent',
                userId: auth.session.userId,
                details: { inviteId: params.id, emailed: result.emailed },
            });
            return json({
                data: { success: true, emailed: result.emailed, inviteUrl: result.inviteUrl },
            });
        }
        catch (err) {
            return internalError(err, 'resend invite');
        }
    });
    router.post('/invites/:id/cancel', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'invite'))
                return modelNotAvailable('invite');
            const result = await cancelInvite(d, params.id);
            if (!result.success) {
                return errorResponse(result.error ?? 'Could not cancel invitation', 400);
            }
            await logEvent({
                event: 'invite_cancelled',
                userId: auth.session.userId,
                details: { inviteId: params.id },
            });
            return json({ data: { success: true } });
        }
        catch (err) {
            return internalError(err, 'cancel invite');
        }
    });
    router.patch('/invites/:id', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'invite'))
                return modelNotAvailable('invite');
            const body = (await request.json());
            if (body.role !== undefined) {
                if (body.role.toUpperCase() === 'OWNER' && auth.session.role !== 'OWNER') {
                    return errorResponse('Only an Owner can assign the Owner role.', 403);
                }
                const result = await updateInviteRole(d, params.id, body.role);
                if (!result.success) {
                    return errorResponse(result.error ?? 'Could not update invitation', 400);
                }
                await logEvent({
                    event: 'invite_role_changed',
                    userId: auth.session.userId,
                    details: { inviteId: params.id, newRole: result.invite?.role },
                });
                return json({ data: { success: true, invite: result.invite } });
            }
            if (body.extendDays !== undefined) {
                const result = await extendInvite(d, params.id, body.extendDays);
                if (!result.success) {
                    return errorResponse(result.error ?? 'Could not extend invitation', 400);
                }
                await logEvent({
                    event: 'invite_extended',
                    userId: auth.session.userId,
                    details: { inviteId: params.id, expiresAt: result.invite?.expiresAt },
                });
                return json({ data: { success: true, invite: result.invite } });
            }
            return errorResponse('Nothing to update', 400);
        }
        catch (err) {
            return internalError(err, 'update invite');
        }
    });
    // ---------------------------------------------------------------------------
    // Access review routes
    // ---------------------------------------------------------------------------
    router.get('/access-review', async (request) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            const d = db();
            if (!hasModel(d, 'user'))
                return modelNotAvailable('user');
            // Dismissals are persisted as audit events — no extra table needed.
            let dismissedIds = [];
            try {
                const rows = await d.auditLog.findMany({
                    where: { event: 'access_review_dismissed' },
                    select: { details: true },
                });
                dismissedIds = rows
                    .map((r) => r.details?.recommendationId)
                    .filter((x) => typeof x === 'string');
            }
            catch {
                dismissedIds = [];
            }
            const recommendations = await computeAccessReview(d, {
                ...teamOptionsFor(auth.session),
                dismissedIds,
            });
            return json({ data: recommendations });
        }
        catch (err) {
            return internalError(err, 'access review');
        }
    });
    router.post('/access-review/:id/dismiss', async (request, params) => {
        try {
            const auth = await requireAuth(request);
            if (auth.error)
                return auth.error;
            const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
            if (roleErr)
                return roleErr;
            await logEvent({
                event: 'access_review_dismissed',
                userId: auth.session.userId,
                details: { recommendationId: params.id },
            });
            return json({ data: { success: true } });
        }
        catch (err) {
            return internalError(err, 'dismiss access review');
        }
    });
}
//# sourceMappingURL=users.js.map