/**
 * DB-backed email delivery configuration store (Settings > Email).
 *
 * Storage: a single Document row with `collection = '__email_config__'`,
 * mirroring the AI config-store pattern (`src/ai/config-store.ts`) so no
 * Prisma migration is required.
 *
 * Security invariants:
 *   - API keys and SMTP passwords are encrypted at rest via `encryptSecret`.
 *   - Encrypted values are NEVER returned to the client; handlers serialize
 *     through {@link toPublicEmailConfig} (masked last-4 + has-flags only).
 *   - Sending with these settings requires the provider factory registered by
 *     `@actuate-media/plugin-email` (dependency direction stays plugin → core).
 */
import { decryptSecret, encryptSecret } from '../security/secret-storage.js';
export const EMAIL_CONFIG_COLLECTION = '__email_config__';
export const EMAIL_PROVIDER_KEYS = ['resend', 'smtpcom', 'ses', 'smtp'];
export function isEmailProviderKey(value) {
    return typeof value === 'string' && EMAIL_PROVIDER_KEYS.includes(value);
}
export const EMAIL_PROVIDER_LABELS = {
    resend: 'Resend',
    smtpcom: 'SMTP.com',
    ses: 'Amazon SES',
    smtp: 'SMTP server',
};
const EMPTY = { provider: null };
/** Last 4 chars of a secret for masked display (never the full value). */
export function emailKeyLast4(key) {
    if (!key)
        return undefined;
    const trimmed = key.trim();
    return trimmed.length >= 4 ? trimmed.slice(-4) : trimmed;
}
/** Read the email config document; `{ provider: null }` when none exists. */
export async function getEmailConfig(db) {
    const d = db;
    if (!d?.document?.findFirst)
        return { ...EMPTY };
    try {
        const doc = await d.document.findFirst({
            where: { collection: EMAIL_CONFIG_COLLECTION, deletedAt: null },
            select: { data: true },
        });
        if (!doc?.data || typeof doc.data !== 'object')
            return { ...EMPTY };
        const data = doc.data;
        return {
            ...data,
            provider: isEmailProviderKey(data.provider) ? data.provider : null,
        };
    }
    catch {
        return { ...EMPTY };
    }
}
/**
 * Upsert the email config document. Caller is responsible for auth/role
 * checks. Stamps `updatedAt`/`updatedBy`.
 */
export async function putEmailConfig(db, next, userId) {
    const d = db;
    const data = {
        ...next,
        updatedAt: new Date().toISOString(),
        updatedBy: userId,
    };
    const existing = await d.document.findFirst({
        where: { collection: EMAIL_CONFIG_COLLECTION, deletedAt: null },
        select: { id: true },
    });
    if (existing) {
        await d.document.update({ where: { id: existing.id }, data: { data, updatedById: userId } });
    }
    else {
        await d.document.create({
            data: {
                collection: EMAIL_CONFIG_COLLECTION,
                data,
                status: 'PUBLISHED',
                title: 'Email settings',
                slug: 'email-config',
                createdById: userId,
                updatedById: userId,
            },
        });
    }
    return data;
}
/** Strip secret material for client serialization. */
export function toPublicEmailConfig(doc) {
    const { apiKeyEncrypted: _key, smtpPassEncrypted: _pass, ...rest } = doc;
    return {
        ...rest,
        hasApiKey: Boolean(doc.apiKeyEncrypted),
        hasSmtpPass: Boolean(doc.smtpPassEncrypted),
    };
}
/**
 * Decrypt the stored config into the plain settings consumed by the provider
 * factory. Returns `null` when no provider is configured or when a required
 * secret cannot be decrypted (wrong/missing `CMS_ENCRYPTION_KEY`) — callers
 * fall back to the env-configured sender rather than sending with bad creds.
 */
export async function resolveEmailProviderSettings(doc) {
    if (!doc.provider)
        return null;
    const from = doc.from?.trim();
    if (!from)
        return null;
    try {
        switch (doc.provider) {
            case 'resend':
            case 'smtpcom': {
                if (!doc.apiKeyEncrypted)
                    return null;
                const apiKey = await decryptSecret(doc.apiKeyEncrypted);
                if (!apiKey)
                    return null;
                return {
                    provider: doc.provider,
                    from,
                    apiKey,
                    ...(doc.provider === 'smtpcom' && doc.channel ? { channel: doc.channel } : {}),
                };
            }
            case 'ses': {
                if (!doc.region)
                    return null;
                return { provider: 'ses', from, region: doc.region };
            }
            case 'smtp': {
                if (!doc.host)
                    return null;
                const pass = doc.smtpPassEncrypted ? await decryptSecret(doc.smtpPassEncrypted) : undefined;
                return {
                    provider: 'smtp',
                    from,
                    host: doc.host,
                    port: doc.port,
                    secure: doc.secure,
                    auth: doc.smtpUser ? { user: doc.smtpUser, pass: pass ?? '' } : undefined,
                };
            }
        }
    }
    catch {
        return null;
    }
}
/** Encrypt a plaintext secret for storage with its masked suffix. */
export async function encryptEmailSecret(plaintext) {
    return { encrypted: await encryptSecret(plaintext), last4: emailKeyLast4(plaintext) ?? '' };
}
/**
 * Which provider (if any) the environment variables currently configure.
 * Mirrors `resolveEmailConfigFromEnv()` in `@actuate-media/plugin-email` so
 * the Settings UI can tell admins what env-based config exists alongside the
 * DB settings.
 */
export function detectEnvEmailProvider(env = process.env) {
    const explicit = env.EMAIL_PROVIDER;
    if (isEmailProviderKey(explicit))
        return explicit;
    if (env.SMTP_HOST)
        return 'smtp';
    if (env.SMTPCOM_API_KEY ?? env.SMTP_KEY)
        return 'smtpcom';
    if (env.RESEND_API_KEY)
        return 'resend';
    return null;
}
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const REGION_RE = /^[a-z]{2}(-[a-z]+)+-\d$/;
// Hostname only — no scheme, path, port suffix, or whitespace.
const HOST_RE = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+$/;
/**
 * Validate a PUT payload against the selected provider's requirements.
 * `hasStoredApiKey`/`hasStoredSmtpPass` let an update keep an existing secret
 * without re-entering it (apiKey omitted = keep, null/'' = clear).
 */
export function validateEmailSettingsInput(input, stored) {
    if (input.provider === null)
        return null; // clearing the config is always valid
    if (!isEmailProviderKey(input.provider)) {
        return 'A valid `provider` is required (resend, smtpcom, ses, smtp) or null to disable.';
    }
    const from = typeof input.from === 'string' ? input.from.trim() : '';
    if (!from || !EMAIL_RE.test(from)) {
        return 'A valid sender address (`from`) is required, e.g. noreply@yoursite.com.';
    }
    const newKey = typeof input.apiKey === 'string' ? input.apiKey.trim() : '';
    const keepsKey = input.apiKey === undefined && stored.hasApiKey;
    switch (input.provider) {
        case 'resend':
        case 'smtpcom':
            if (!newKey && !keepsKey)
                return 'An API key is required for this provider.';
            return null;
        case 'ses': {
            const region = typeof input.region === 'string' ? input.region.trim() : '';
            if (!region || !REGION_RE.test(region)) {
                return 'A valid AWS region is required, e.g. us-east-1.';
            }
            return null;
        }
        case 'smtp': {
            const host = typeof input.host === 'string' ? input.host.trim() : '';
            if (!host || !HOST_RE.test(host)) {
                return 'A valid SMTP hostname is required, e.g. smtp.example.com.';
            }
            if (input.port !== undefined && input.port !== null) {
                const port = Number(input.port);
                if (!Number.isInteger(port) || port < 1 || port > 65535) {
                    return 'SMTP port must be an integer between 1 and 65535.';
                }
            }
            return null;
        }
    }
}
//# sourceMappingURL=settings-store.js.map