import { beforeEach, describe, expect, it } from 'vitest';
import { createInvite, cancelInvite, updateInviteRole, extendInvite, listInvites, markInviteAcceptedForEmail, } from '../../auth/invites.js';
import { setEmailSender } from '../../email/sender.js';
function createDB() {
    const users = [];
    const invites = [];
    const tokens = [];
    const matchEmail = (where, email) => !where?.email ||
        (where.email.equals
            ? String(where.email.equals).toLowerCase() === email.toLowerCase()
            : where.email === email);
    return {
        users,
        invites,
        tokens,
        user: {
            findFirst: async ({ where }) => users.find((u) => matchEmail(where, u.email) &&
                (where?.acceptedInviteAt === null ? u.acceptedInviteAt == null : true)) ?? null,
            create: async ({ data }) => {
                const u = { id: `u_${users.length + 1}`, isActive: true, ...data };
                users.push(u);
                return u;
            },
            update: async ({ where, data }) => {
                const u = users.find((x) => x.id === where.id);
                Object.assign(u, data);
                return u;
            },
            updateMany: async ({ where, data }) => {
                let n = 0;
                for (const u of users) {
                    if (matchEmail(where, u.email) &&
                        (where?.acceptedInviteAt === null ? u.acceptedInviteAt == null : true)) {
                        Object.assign(u, data);
                        n++;
                    }
                }
                return { count: n };
            },
        },
        invite: {
            findFirst: async ({ where }) => invites.find((i) => matchEmail(where, i.email) && (!where?.status || i.status === where.status)) ?? null,
            findUnique: async ({ where }) => invites.find((i) => i.id === where.id) ?? null,
            findMany: async ({ where } = {}) => invites.filter((i) => !where?.status || i.status === where.status),
            create: async ({ data }) => {
                const i = { id: `inv_${invites.length + 1}`, createdAt: new Date(), ...data };
                invites.push(i);
                return i;
            },
            update: async ({ where, data }) => {
                const i = invites.find((x) => x.id === where.id);
                Object.assign(i, data);
                return i;
            },
            updateMany: async ({ where, data }) => {
                let n = 0;
                for (const i of invites) {
                    const statusOk = !where?.status || i.status === where.status;
                    const emailOk = matchEmail(where, i.email);
                    const expiredOk = where?.expiresAt?.lt ? i.expiresAt < where.expiresAt.lt : true;
                    if (statusOk && emailOk && expiredOk) {
                        Object.assign(i, data);
                        n++;
                    }
                }
                return { count: n };
            },
        },
        passwordResetToken: {
            create: async ({ data }) => {
                const t = { id: `rt_${tokens.length + 1}`, ...data };
                tokens.push(t);
                return t;
            },
            updateMany: async ({ where, data }) => {
                let n = 0;
                for (const t of tokens) {
                    if (where?.usedAt === null ? t.usedAt == null : true) {
                        Object.assign(t, data);
                        n++;
                    }
                }
                return { count: n };
            },
        },
    };
}
const CONFIG = {
    siteUrl: 'https://cms.example.com',
    adminPath: '/admin',
    invitedByUserId: 'admin-1',
};
describe('invite service', () => {
    beforeEach(() => setEmailSender(null));
    it('creates a placeholder user, a PENDING invite and a single-use token (hashed at rest)', async () => {
        const db = createDB();
        const res = await createInvite(db, { email: 'New@Example.com', role: 'editor' }, CONFIG);
        expect(res.success).toBe(true);
        expect(res.emailed).toBe(false);
        expect(res.inviteUrl).toContain('/admin/reset-password?token=');
        expect(db.invites).toHaveLength(1);
        expect(db.invites[0].status).toBe('PENDING');
        expect(db.invites[0].email).toBe('new@example.com');
        expect(db.users).toHaveLength(1);
        expect(db.users[0].isApproved).toBe(false);
        const rawToken = res.inviteUrl.split('token=')[1];
        expect(db.invites[0].tokenHash).not.toBe(rawToken);
        expect(db.tokens[0].tokenHash).toBe(db.invites[0].tokenHash);
    });
    it('rejects a second invite to the same email (PENDING_INVITE)', async () => {
        const db = createDB();
        await createInvite(db, { email: 'a@example.com', role: 'editor' }, CONFIG);
        const res = await createInvite(db, { email: 'a@example.com', role: 'viewer' }, CONFIG);
        expect(res.success).toBe(false);
        expect(res.code).toBe('PENDING_INVITE');
        expect(db.invites).toHaveLength(1);
    });
    it('rejects inviting an existing user (DUPLICATE_USER)', async () => {
        const db = createDB();
        db.users.push({ id: 'u1', email: 'taken@example.com', acceptedInviteAt: new Date() });
        const res = await createInvite(db, { email: 'taken@example.com', role: 'editor' }, CONFIG);
        expect(res.code).toBe('DUPLICATE_USER');
    });
    it('rejects invalid email and role', async () => {
        const db = createDB();
        expect((await createInvite(db, { email: 'nope', role: 'editor' }, CONFIG)).code).toBe('INVALID_EMAIL');
        expect((await createInvite(db, { email: 'a@example.com', role: 'wizard' }, CONFIG)).code).toBe('INVALID_ROLE');
    });
    it('cancels an invite and deactivates its placeholder user', async () => {
        const db = createDB();
        await createInvite(db, { email: 'c@example.com', role: 'editor' }, CONFIG);
        const id = db.invites[0].id;
        const res = await cancelInvite(db, id);
        expect(res.success).toBe(true);
        expect(db.invites[0].status).toBe('CANCELLED');
        expect(db.users[0].isActive).toBe(false);
    });
    it('changes an invite role', async () => {
        const db = createDB();
        await createInvite(db, { email: 'r@example.com', role: 'viewer' }, CONFIG);
        const res = await updateInviteRole(db, db.invites[0].id, 'admin');
        expect(res.success).toBe(true);
        expect(db.invites[0].role).toBe('ADMIN');
        expect(db.users[0].role).toBe('ADMIN');
    });
    it('extends an invite expiry', async () => {
        const db = createDB();
        await createInvite(db, { email: 'e@example.com', role: 'editor' }, CONFIG);
        const before = new Date(db.invites[0].expiresAt).getTime();
        const res = await extendInvite(db, db.invites[0].id, 30);
        expect(res.success).toBe(true);
        expect(new Date(db.invites[0].expiresAt).getTime()).toBeGreaterThan(before);
    });
    it('marks the pending invite ACCEPTED for an email', async () => {
        const db = createDB();
        await createInvite(db, { email: 'acc@example.com', role: 'editor' }, CONFIG);
        const ok = await markInviteAcceptedForEmail(db, 'acc@example.com', 'u_1');
        expect(ok).toBe(true);
        expect(db.invites[0].status).toBe('ACCEPTED');
        expect(db.invites[0].acceptedUserId).toBe('u_1');
    });
    it('lists invites and lazily expires stale PENDING rows', async () => {
        const db = createDB();
        await createInvite(db, { email: 's@example.com', role: 'editor' }, CONFIG);
        // Force expiry into the past.
        db.invites[0].expiresAt = new Date(Date.now() - 1000);
        const list = await listInvites(db);
        expect(list[0].status).toBe('EXPIRED');
        expect(list[0].expired).toBe(true);
    });
    it('emails the invite when a sender is registered (no link returned)', async () => {
        const db = createDB();
        const sent = [];
        setEmailSender({ send: async (o) => void sent.push(o.to) });
        const res = await createInvite(db, { email: 'mail@example.com', role: 'editor' }, CONFIG);
        expect(res.emailed).toBe(true);
        expect(res.inviteUrl).toBeUndefined();
        expect(sent).toEqual(['mail@example.com']);
    });
});
//# sourceMappingURL=invites.test.js.map