import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { handleActuateAPI } from '../../api/index.js';
import { initDB } from '../../db.js';
import { generateApiKey } from '../../security/api-key-enhanced.js';
import { clearEmailSenderCache } from '../../email/resolve.js';
import { setEmailProviderFactory, setEmailSender } from '../../email/sender.js';
import { EMAIL_CONFIG_COLLECTION } from '../../email/settings-store.js';
/**
 * Settings > Email API contract:
 *   - GET returns masked settings (encrypted secrets never serialized);
 *   - PUT validates per provider, encrypts secrets at rest, keeps an existing
 *     key when `apiKey` is omitted on the same provider, and wipes secrets
 *     when switching providers or clearing;
 *   - POST /test sends only to the signed-in admin's own address.
 */
const VALID_SECRET = 'a'.repeat(48);
function createMockDB(apiKey) {
    let configRow = null;
    const db = {
        apiKey: {
            findMany: async () => [apiKey],
            findUnique: async ({ where }) => apiKey.keyHash === where.keyHash ? apiKey : null,
            update: async () => apiKey,
        },
        document: {
            findFirst: async ({ where, select }) => {
                if (where?.collection !== EMAIL_CONFIG_COLLECTION || !configRow)
                    return null;
                if (select?.id)
                    return { id: configRow.id };
                return { data: configRow.data };
            },
            update: async ({ data }) => {
                configRow = { id: configRow.id, data: data.data };
                return configRow;
            },
            create: async ({ data }) => {
                configRow = { id: 'email-config-1', data: data.data };
                return configRow;
            },
        },
        user: {
            findMany: async () => [],
            findUnique: async ({ where }) => where?.id === 'user-1' ? { email: 'admin@example.com', name: 'Admin' } : null,
        },
        auditLog: { create: async () => ({}) },
        session: {},
    };
    return { db, getConfigRow: () => configRow };
}
async function makeKey() {
    const { key, keyHash, keyPrefix } = await generateApiKey({
        prefix: 'act_sk',
        scopes: { admin: true },
    });
    return {
        key,
        record: {
            id: 'apikey-1',
            keyHash,
            keyPrefix,
            userId: 'user-1',
            scopes: { admin: true },
            ipRestrictions: null,
            expiresAt: null,
            lastUsedAt: null,
            revokedAt: null,
        },
    };
}
function apiRequest(key, path, method = 'GET', body) {
    return new Request(`https://example.com/api/cms${path}`, {
        method,
        headers: {
            Authorization: `Bearer ${key}`,
            ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
        },
        ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
    });
}
describe('Settings > Email API', () => {
    beforeEach(() => {
        delete globalThis.__actuateConfig;
        process.env.CMS_SECRET = VALID_SECRET;
        process.env.CMS_ENCRYPTION_KEY = 'b'.repeat(64);
        delete process.env.EMAIL_PROVIDER;
        delete process.env.RESEND_API_KEY;
        delete process.env.SMTP_HOST;
        delete process.env.SMTP_KEY;
        delete process.env.SMTPCOM_API_KEY;
        setEmailSender(null);
        setEmailProviderFactory(null);
        clearEmailSenderCache();
    });
    afterEach(() => {
        setEmailSender(null);
        setEmailProviderFactory(null);
        clearEmailSenderCache();
        delete process.env.CMS_ENCRYPTION_KEY;
    });
    it('GET returns empty settings with environment metadata', async () => {
        const { key, record } = await makeKey();
        const { db } = createMockDB(record);
        initDB(db);
        const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
        process.env.RESEND_API_KEY = 're_env_key';
        const res = await handler(apiRequest(key, '/email/settings'));
        expect(res.status).toBe(200);
        const body = (await res.json());
        expect(body.data.settings).toMatchObject({ provider: null, hasApiKey: false });
        expect(body.data.envProvider).toBe('resend');
        expect(body.data.factoryAvailable).toBe(false);
        expect(body.data.platformAdapter).toBe(false);
    });
    it('PUT stores settings with the API key encrypted and never echoes it back', async () => {
        const { key, record } = await makeKey();
        const { db, getConfigRow } = createMockDB(record);
        initDB(db);
        const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
        const res = await handler(apiRequest(key, '/email/settings', 'PUT', {
            provider: 'smtpcom',
            from: 'noreply@example.com',
            apiKey: 'smtpcom-secret-key',
            channel: 'transactional',
        }));
        expect(res.status).toBe(200);
        const body = (await res.json());
        expect(body.data.settings.provider).toBe('smtpcom');
        expect(body.data.settings.hasApiKey).toBe(true);
        expect(body.data.settings.apiKeyLast4).toBe('-key');
        expect(JSON.stringify(body)).not.toContain('smtpcom-secret-key');
        const row = getConfigRow();
        expect(String(row.data.apiKeyEncrypted)).toMatch(/^enc:v1:/);
        expect(JSON.stringify(row.data)).not.toContain('smtpcom-secret-key');
    });
    it('PUT rejects invalid payloads with a 400', async () => {
        const { key, record } = await makeKey();
        const { db } = createMockDB(record);
        initDB(db);
        const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
        const missingKey = await handler(apiRequest(key, '/email/settings', 'PUT', { provider: 'resend', from: 'a@b.co' }));
        expect(missingKey.status).toBe(400);
        const badFrom = await handler(apiRequest(key, '/email/settings', 'PUT', { provider: 'resend', apiKey: 're_x' }));
        expect(badFrom.status).toBe(400);
        const badProvider = await handler(apiRequest(key, '/email/settings', 'PUT', { provider: 'sendgrid', from: 'a@b.co' }));
        expect(badProvider.status).toBe(400);
    });
    it('PUT keeps the stored key when omitted, and wipes it when switching providers', async () => {
        const { key, record } = await makeKey();
        const { db, getConfigRow } = createMockDB(record);
        initDB(db);
        const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
        await handler(apiRequest(key, '/email/settings', 'PUT', {
            provider: 'resend',
            from: 'a@b.co',
            apiKey: 're_first_key',
        }));
        const encryptedFirst = getConfigRow().data.apiKeyEncrypted;
        // Update the sender address only — key omitted, must be preserved.
        const update = await handler(apiRequest(key, '/email/settings', 'PUT', { provider: 'resend', from: 'new@b.co' }));
        expect(update.status).toBe(200);
        expect(getConfigRow().data.apiKeyEncrypted).toBe(encryptedFirst);
        expect(getConfigRow().data.from).toBe('new@b.co');
        // Switching providers must NOT silently reuse the old secret.
        const switched = await handler(apiRequest(key, '/email/settings', 'PUT', { provider: 'smtpcom', from: 'new@b.co' }));
        expect(switched.status).toBe(400); // requires a new key
        // Clearing wipes everything.
        const cleared = await handler(apiRequest(key, '/email/settings', 'PUT', { provider: null }));
        expect(cleared.status).toBe(200);
        expect(getConfigRow().data.apiKeyEncrypted).toBeUndefined();
        expect(getConfigRow().data.provider).toBeNull();
    });
    it('POST /test sends to the signed-in admin via the DB-configured provider', async () => {
        const { key, record } = await makeKey();
        const { db } = createMockDB(record);
        initDB(db);
        const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
        const sent = [];
        setEmailProviderFactory((settings) => {
            expect(settings.provider).toBe('resend');
            expect(settings.apiKey).toBe('re_test_key');
            return {
                send: async (opts) => {
                    sent.push({ to: opts.to, subject: opts.subject });
                },
            };
        });
        await handler(apiRequest(key, '/email/settings', 'PUT', {
            provider: 'resend',
            from: 'a@b.co',
            apiKey: 're_test_key',
        }));
        const res = await handler(apiRequest(key, '/email/settings/test', 'POST', {}));
        expect(res.status).toBe(200);
        const body = (await res.json());
        expect(body.data).toMatchObject({ success: true, sentTo: 'admin@example.com' });
        expect(sent).toEqual([{ to: 'admin@example.com', subject: expect.stringContaining('test') }]);
    });
    it('POST /test returns 400 when nothing can send and 502 when the send fails', async () => {
        const { key, record } = await makeKey();
        const { db } = createMockDB(record);
        initDB(db);
        const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
        const none = await handler(apiRequest(key, '/email/settings/test', 'POST', {}));
        expect(none.status).toBe(400);
        setEmailSender({
            send: async () => {
                throw new Error('SMTP.com API error (401): bad key');
            },
        });
        const failed = await handler(apiRequest(key, '/email/settings/test', 'POST', {}));
        expect(failed.status).toBe(502);
        const body = (await failed.json());
        expect(body.error).toContain('SMTP.com API error');
    });
    it('rejects non-admin scopes', async () => {
        const readScopes = { collections: ['*'], actions: ['read'] };
        const { key: readKey, keyHash, keyPrefix, } = await generateApiKey({
            prefix: 'act_sk',
            scopes: readScopes,
        });
        const record = {
            id: 'apikey-2',
            keyHash,
            keyPrefix,
            userId: 'user-2',
            scopes: readScopes,
            ipRestrictions: null,
            expiresAt: null,
            lastUsedAt: null,
            revokedAt: null,
        };
        const { db } = createMockDB(record);
        initDB(db);
        const handler = handleActuateAPI({ prismaClient: db, config: { collections: {} } });
        const res = await handler(apiRequest(readKey, '/email/settings'));
        expect(res.status).toBe(403);
    });
});
//# sourceMappingURL=email-settings-routes.test.js.map