import { beforeEach, describe, expect, it } from 'vitest';
import { handleActuateAPI } from '../../api/index.js';
import { createSession } from '../../auth/session.js';
import { initDB } from '../../db.js';
import { generateToken as generateCsrfToken } from '../../security/csrf.js';
/**
 * Integration tests for the comment REST endpoints introduced in slice 4
 * of Phase 3. The point of these tests is to exercise the *handler* layer
 * end-to-end (auth, CSRF, JSON envelopes, status code mapping) — the pure
 * service logic is covered by `realtime/comments.test.ts`.
 *
 * We back the mock DB with a tiny in-memory implementation of the subset
 * of Prisma we use, so the routes execute against real-ish data without a
 * Postgres instance.
 */
const SECRET = 'test-secret-for-comments-routes-1234567890abcd';
function createMockDB() {
    const rows = new Map();
    let counter = 0;
    return {
        rows,
        session: {
            findUnique: async () => ({ revokedAt: null }),
        },
        user: {},
        media: {},
        documentComment: {
            async findUnique({ where }) {
                return rows.get(where.id) ?? null;
            },
            async findMany({ where, orderBy, take, }) {
                let r = Array.from(rows.values()).filter((row) => {
                    if (where.documentId !== undefined && row.documentId !== where.documentId)
                        return false;
                    if (Object.prototype.hasOwnProperty.call(where, 'resolvedAt') && row.resolvedAt !== null)
                        return false;
                    if (Object.prototype.hasOwnProperty.call(where, 'deletedAt') && row.deletedAt !== null)
                        return false;
                    return true;
                });
                if (orderBy?.createdAt === 'asc') {
                    r = r.sort((a, b) => {
                        const ax = a.createdAt instanceof Date ? a.createdAt.getTime() : 0;
                        const bx = b.createdAt instanceof Date ? b.createdAt.getTime() : 0;
                        return ax - bx;
                    });
                }
                if (typeof take === 'number')
                    r = r.slice(0, take);
                return r;
            },
            async create({ data, }) {
                counter += 1;
                const now = new Date(Date.UTC(2026, 5, 1, 0, 0, counter));
                const row = {
                    id: `c-${counter}`,
                    documentId: data.documentId,
                    parentId: data.parentId,
                    userId: data.userId,
                    body: data.body,
                    anchor: data.anchor,
                    resolvedAt: null,
                    resolvedById: null,
                    deletedAt: null,
                    createdAt: now,
                    updatedAt: now,
                };
                rows.set(row.id, row);
                return row;
            },
            async update({ where, data }) {
                const existing = rows.get(where.id);
                if (!existing)
                    throw new Error(`row ${where.id} not found`);
                const next = {
                    ...existing,
                    ...data,
                    updatedAt: new Date(),
                };
                rows.set(where.id, next);
                return next;
            },
        },
        auditLog: {
            // logEvent does fire-and-forget writes; we just accept anything.
            create: async () => ({}),
        },
    };
}
async function sessionHeaders(role, userId = 'user-1') {
    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',
    };
}
describe('comments REST endpoints (Phase 3 slice 4)', () => {
    let db;
    let handler;
    beforeEach(() => {
        process.env.CMS_SECRET = SECRET;
        db = createMockDB();
        initDB(db);
        handler = handleActuateAPI({ prismaClient: db });
    });
    it('rejects unauthenticated POST', async () => {
        const res = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments', {
            method: 'POST',
            // No session cookie — auth should reject before CSRF runs.
            headers: { 'content-type': 'application/json' },
            body: JSON.stringify({ body: 'hi' }),
        }));
        // CSRF runs *before* auth, so an unauthenticated POST without a CSRF
        // token returns 403 instead of 401. Either status is fine for this
        // test — we just want to confirm anonymous writes are blocked.
        expect([401, 403]).toContain(res.status);
    });
    it('requires write role for POST', async () => {
        const res = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments', {
            method: 'POST',
            headers: await sessionHeaders('VIEWER'),
            body: JSON.stringify({ body: 'hi' }),
        }));
        expect(res.status).toBe(403);
    });
    it('creates a top-level comment for an editor', async () => {
        const res = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments', {
            method: 'POST',
            headers: await sessionHeaders('EDITOR'),
            body: JSON.stringify({ body: 'Looks great!', anchor: { from: 'AQ==', to: 'Ag==' } }),
        }));
        expect(res.status).toBe(201);
        const json = (await res.json());
        expect(json.data.body).toBe('Looks great!');
        expect(json.data.anchor).toEqual({ from: 'AQ==', to: 'Ag==' });
    });
    it('returns 400 on empty body', async () => {
        const res = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments', {
            method: 'POST',
            headers: await sessionHeaders('EDITOR'),
            body: JSON.stringify({ body: '   ' }),
        }));
        expect(res.status).toBe(400);
    });
    it('lists active comments only by default', async () => {
        const headers = await sessionHeaders('EDITOR');
        await handler(new Request('https://example.com/api/cms/documents/doc-1/comments', {
            method: 'POST',
            headers,
            body: JSON.stringify({ body: 'open one' }),
        }));
        const createRes = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments', {
            method: 'POST',
            headers,
            body: JSON.stringify({ body: 'will resolve' }),
        }));
        const created = (await createRes.json());
        await handler(new Request(`https://example.com/api/cms/comments/${created.data.id}/resolve`, {
            method: 'POST',
            headers,
        }));
        const listRes = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments', {
            headers: { cookie: headers.cookie },
        }));
        expect(listRes.status).toBe(200);
        const list = (await listRes.json());
        expect(list.data.map((c) => c.body)).toEqual(['open one']);
        const listAll = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments?includeResolved=true', {
            headers: { cookie: headers.cookie },
        }));
        const allBodies = (await listAll.json()).data.map((c) => c.body);
        expect(allBodies).toContain('will resolve');
    });
    it('blocks includeDeleted for non-admins', async () => {
        const res = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments?includeDeleted=true', {
            headers: { cookie: (await sessionHeaders('EDITOR')).cookie },
        }));
        expect(res.status).toBe(403);
    });
    it('lets admins request includeDeleted', async () => {
        const res = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments?includeDeleted=true', {
            headers: { cookie: (await sessionHeaders('ADMIN')).cookie },
        }));
        expect(res.status).toBe(200);
    });
    it('allows the author to edit but blocks other editors', async () => {
        const authorHeaders = await sessionHeaders('EDITOR', 'user-1');
        const otherHeaders = await sessionHeaders('EDITOR', 'user-2');
        const create = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments', {
            method: 'POST',
            headers: authorHeaders,
            body: JSON.stringify({ body: 'mine' }),
        }));
        const created = (await create.json());
        const evil = await handler(new Request(`https://example.com/api/cms/comments/${created.data.id}`, {
            method: 'PATCH',
            headers: otherHeaders,
            body: JSON.stringify({ body: 'tampered' }),
        }));
        expect(evil.status).toBe(403);
        const good = await handler(new Request(`https://example.com/api/cms/comments/${created.data.id}`, {
            method: 'PATCH',
            headers: authorHeaders,
            body: JSON.stringify({ body: 'fixed' }),
        }));
        expect(good.status).toBe(200);
        const dto = (await good.json());
        expect(dto.data.body).toBe('fixed');
    });
    it('resolves and re-opens a thread, then soft-deletes it', async () => {
        const headers = await sessionHeaders('EDITOR');
        const create = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments', {
            method: 'POST',
            headers,
            body: JSON.stringify({ body: 'thread' }),
        }));
        const { data } = (await create.json());
        const resolved = await handler(new Request(`https://example.com/api/cms/comments/${data.id}/resolve`, {
            method: 'POST',
            headers,
        }));
        expect(resolved.status).toBe(200);
        const resolvedDto = (await resolved.json());
        expect(resolvedDto.data.isResolved).toBe(true);
        const reopened = await handler(new Request(`https://example.com/api/cms/comments/${data.id}/reopen`, {
            method: 'POST',
            headers,
        }));
        expect(reopened.status).toBe(200);
        const deleted = await handler(new Request(`https://example.com/api/cms/comments/${data.id}`, {
            method: 'DELETE',
            headers,
        }));
        expect(deleted.status).toBe(200);
        const deletedDto = (await deleted.json());
        expect(deletedDto.data.deletedAt).not.toBeNull();
    });
    it('returns 404 when patching a missing comment', async () => {
        const res = await handler(new Request('https://example.com/api/cms/comments/does-not-exist', {
            method: 'PATCH',
            headers: await sessionHeaders('EDITOR'),
            body: JSON.stringify({ body: 'ghost' }),
        }));
        expect(res.status).toBe(404);
    });
    it('threads a reply through the create endpoint', async () => {
        const headers = await sessionHeaders('EDITOR');
        const create = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments', {
            method: 'POST',
            headers,
            body: JSON.stringify({ body: 'root' }),
        }));
        const root = (await create.json()).data;
        const reply = await handler(new Request('https://example.com/api/cms/documents/doc-1/comments', {
            method: 'POST',
            headers,
            body: JSON.stringify({ body: 'reply', parentId: root.id }),
        }));
        expect(reply.status).toBe(201);
        const replyDto = (await reply.json());
        expect(replyDto.data.parentId).toBe(root.id);
    });
});
//# sourceMappingURL=comments-routes.test.js.map