import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { handleActuateAPI } from '../../api/index.js';
import { createSession } from '../../auth/session.js';
import { generateToken as generateCsrfToken } from '../../security/csrf.js';
import { initDB } from '../../db.js';
import { setEmailSender } from '../../email/sender.js';
/**
 * Integration tests for `POST /api/cms/users/invite`.
 *
 * The endpoint records a first-class Invite (hashed single-use token) and
 * provisions a placeholder user, then returns a set-password link (and/or
 * emails it). It is admin-only, seat-aware, and Owner-gated for the Owner role.
 */
const SECRET = 'test-secret-for-users-invite-routes-1234567890abc';
function createMockDB(rows) {
    const users = rows.map((r) => ({ isActive: true, ...r }));
    const tokens = [];
    const invites = [];
    const invite = {
        findMany: async ({ where } = {}) => {
            let list = invites;
            if (where?.status)
                list = list.filter((i) => i.status === where.status);
            return list;
        },
        findFirst: async ({ where }) => {
            if (where?.email?.equals) {
                const target = String(where.email.equals).toLowerCase();
                return (invites.find((i) => i.email.toLowerCase() === target && (!where.status || i.status === where.status)) ?? null);
            }
            return null;
        },
        findUnique: async ({ where }) => invites.find((i) => i.id === where.id) ?? null,
        count: async ({ where } = {}) => {
            let list = invites;
            if (where?.status)
                list = list.filter((i) => i.status === where.status);
            return list.length;
        },
        create: async ({ data }) => {
            const row = {
                id: `inv_${invites.length + 1}`,
                createdAt: new Date(),
                ...data,
            };
            invites.push(row);
            return row;
        },
        update: async ({ where, data }) => {
            const row = invites.find((i) => i.id === where.id);
            Object.assign(row, data);
            return row;
        },
        updateMany: async ({ where, data }) => {
            let n = 0;
            for (const i of invites) {
                if (!where?.status || i.status === where.status) {
                    Object.assign(i, data);
                    n++;
                }
            }
            return { count: n };
        },
    };
    return {
        _users: users,
        _tokens: tokens,
        _invites: invites,
        session: { findUnique: async () => ({ revokedAt: null }) },
        user: {
            findMany: async () => users,
            findUnique: async ({ where }) => users.find((u) => u.id === where.id) ?? null,
            count: async ({ where } = {}) => users.filter((u) => (where?.role ? u.role === where.role : true) &&
                (where?.isActive !== undefined ? (u.isActive ?? true) === where.isActive : true)).length,
            findFirst: async ({ where }) => {
                const target = String(where.email.equals).toLowerCase();
                return users.find((u) => u.email.toLowerCase() === target) ?? null;
            },
            create: async ({ data }) => {
                const u = { id: `u_${users.length + 1}`, isActive: true, ...data };
                users.push(u);
                return u;
            },
        },
        passwordResetToken: {
            create: async ({ data }) => {
                const t = { id: `rt_${tokens.length + 1}`, ...data };
                tokens.push(t);
                return t;
            },
        },
        invite,
    };
}
async function roleHeaders(userId, role) {
    const token = await createSession({ userId, role, sessionId: `session-${userId}` }, { secret: SECRET });
    const csrf = await generateCsrfToken();
    return {
        cookie: `actuate_session=${token}; actuate_csrf=${csrf}`,
        'x-csrf-token': csrf,
        'content-type': 'application/json',
    };
}
const adminHeaders = (userId) => roleHeaders(userId, 'ADMIN');
const editorHeaders = (userId) => roleHeaders(userId, 'EDITOR');
describe('POST /api/cms/users/invite', () => {
    const original = {
        CMS_SECRET: process.env.CMS_SECRET,
        CMS_SESSION_SECRET: process.env.CMS_SESSION_SECRET,
        NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
    };
    beforeEach(() => {
        process.env.CMS_SECRET = SECRET;
        delete process.env.CMS_SESSION_SECRET;
        process.env.NEXT_PUBLIC_SITE_URL = 'https://cms.example.com';
        setEmailSender(null);
    });
    afterEach(() => {
        setEmailSender(null);
        for (const [k, v] of Object.entries(original)) {
            if (v === undefined)
                delete process.env[k];
            else
                process.env[k] = v;
        }
    });
    function makeHandler(rows = []) {
        const db = createMockDB(rows);
        initDB(db);
        return { handler: handleActuateAPI({ prismaClient: db }), db };
    }
    function inviteRequest(headers, body) {
        return new Request('https://cms.example.com/api/cms/users/invite', {
            method: 'POST',
            headers,
            body: JSON.stringify(body),
        });
    }
    it('rejects anonymous writes (401 from auth or 403 from CSRF)', async () => {
        const { handler } = makeHandler();
        const res = await handler(new Request('https://cms.example.com/api/cms/users/invite', {
            method: 'POST',
            headers: { 'content-type': 'application/json' },
            body: JSON.stringify({ email: 'a@example.com', role: 'editor' }),
        }));
        expect([401, 403]).toContain(res.status);
    });
    it('rejects non-admin roles with 403', async () => {
        const { handler, db } = makeHandler([
            { id: 'editor-1', email: 'ed@example.com', name: 'Ed', role: 'EDITOR' },
        ]);
        const res = await handler(inviteRequest(await editorHeaders('editor-1'), { email: 'a@example.com', role: 'editor' }));
        expect(res.status).toBe(403);
        expect(db._invites).toHaveLength(0);
    });
    it('records an invite + token and returns a shareable link when email is unconfigured', async () => {
        const { handler, db } = makeHandler([
            { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
        ]);
        const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'new@example.com', role: 'editor' }));
        expect(res.status).toBe(200);
        const body = (await res.json());
        expect(body.data.success).toBe(true);
        expect(body.data.emailed).toBe(false);
        expect(body.data.invite.role).toBe('EDITOR');
        expect(body.data.inviteUrl).toContain('/admin/reset-password?token=');
        expect(db._tokens).toHaveLength(1);
        expect(db._invites).toHaveLength(1);
        // The hashed token is stored — never the raw token.
        expect(db._invites[0].tokenHash).not.toContain(body.data.inviteUrl.split('token=')[1]);
        expect(db._users.map((u) => u.email)).toContain('new@example.com');
    });
    it('emails the invite (and hides the link) when a sender is registered', async () => {
        const sent = [];
        setEmailSender({
            send: async (o) => {
                sent.push({ to: o.to });
            },
        });
        const { handler } = makeHandler([
            { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
        ]);
        const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'new@example.com', role: 'viewer' }));
        const body = (await res.json());
        expect(body.data.emailed).toBe(true);
        expect(body.data.inviteUrl).toBeUndefined();
        expect(sent).toEqual([{ to: 'new@example.com' }]);
    });
    it('rejects a duplicate existing user with 400', async () => {
        const { handler, db } = makeHandler([
            { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
            { id: 'u-2', email: 'taken@example.com', name: 'Taken', role: 'EDITOR' },
        ]);
        const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'Taken@Example.com', role: 'editor' }));
        expect(res.status).toBe(400);
        expect(db._invites).toHaveLength(0);
    });
    it('rejects an invalid role with 400', async () => {
        const { handler } = makeHandler([
            { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
        ]);
        const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'a@example.com', role: 'superuser' }));
        expect(res.status).toBe(400);
    });
    it('forbids a non-Owner from inviting an Owner (403)', async () => {
        const { handler, db } = makeHandler([
            { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
        ]);
        const res = await handler(inviteRequest(await adminHeaders('admin-1'), { email: 'owner@example.com', role: 'owner' }));
        expect(res.status).toBe(403);
        expect(db._invites).toHaveLength(0);
    });
    it('requires an email', async () => {
        const { handler } = makeHandler([
            { id: 'admin-1', email: 'admin@example.com', name: 'Admin', role: 'ADMIN' },
        ]);
        const res = await handler(inviteRequest(await adminHeaders('admin-1'), { role: 'editor' }));
        expect(res.status).toBe(400);
    });
});
//# sourceMappingURL=users-invite.test.js.map