/**
 * DB-backed AI configuration store.
 *
 * Storage: a single Document row with `collection = '__ai_config__'` holding
 * `{ providers, settings, catalog }`, mirroring the SEO config-store pattern
 * (`packages/cms-core/src/seo/config-store.ts`). This keeps Phases 1-2 free of a
 * Prisma migration. The per-run usage table arrives in Phase 4.
 *
 * Security invariants:
 *   - Provider API keys are encrypted at rest via `encryptSecret`.
 *   - The encrypted key is NEVER returned to the client; handlers serialize
 *     providers through {@link toPublicProvider}.
 *   - When a provider's env var is set, that key wins and the UI shows it as
 *     env-managed/read-only.
 */
import { decryptSecret, encryptSecret } from '../security/secret-storage.js';
import { cloneFeatureDefaults, mergeFeatures } from './feature-defaults.js';
export const AI_CONFIG_COLLECTION = '__ai_config__';
/** Env var that supplies each built-in provider's key (custom has none). */
const ENV_KEY_BY_PROVIDER = {
    openai: 'OPENAI_API_KEY',
    anthropic: 'ANTHROPIC_API_KEY',
    xai: 'XAI_API_KEY',
    custom: null,
};
export const DEFAULT_AI_BUDGET = {
    alertAtPercentages: [50, 80, 100],
    disableWhenExceeded: false,
};
export function defaultAISettings() {
    return {
        features: cloneFeatureDefaults(),
        budget: { ...DEFAULT_AI_BUDGET, alertAtPercentages: [...DEFAULT_AI_BUDGET.alertAtPercentages] },
        logsRetentionDays: 30,
    };
}
/** Read the env-managed key for a provider, if present and non-empty. */
export function getEnvApiKey(provider) {
    const envVar = ENV_KEY_BY_PROVIDER[provider];
    if (!envVar)
        return null;
    const val = process.env[envVar];
    return val && val.trim() ? val.trim() : null;
}
/** Last 4 chars of a key for masked display (never the full value). */
export function last4(key) {
    if (!key)
        return undefined;
    const trimmed = key.trim();
    return trimmed.length >= 4 ? trimmed.slice(-4) : trimmed;
}
/**
 * Resolve the effective API key for a connection at call time: an env var (if
 * configured for this provider) always wins, otherwise the decrypted stored key.
 * Returns null when neither exists.
 */
export async function resolveProviderKey(conn) {
    const envKey = getEnvApiKey(conn.provider);
    if (envKey)
        return envKey;
    if (conn.apiKeyEncrypted) {
        try {
            return await decryptSecret(conn.apiKeyEncrypted);
        }
        catch {
            return null;
        }
    }
    return null;
}
/** Determine where a connection's key comes from for UI display. */
export function resolveKeySource(conn) {
    if (getEnvApiKey(conn.provider))
        return 'environment';
    if (conn.apiKeyEncrypted)
        return 'database';
    return 'database';
}
/** Strip key material and add UI-facing source metadata. */
export function toPublicProvider(conn) {
    const envKey = getEnvApiKey(conn.provider);
    const source = resolveKeySource(conn);
    const keyManagedExternally = source === 'environment';
    // Reflect the env key's suffix when env-managed, else the stored suffix.
    const maskLast4 = envKey ? last4(envKey) : conn.apiKeyLast4;
    const { apiKeyEncrypted: _omit, ...rest } = conn;
    return {
        ...rest,
        apiKeyLast4: maskLast4,
        apiKeySource: source,
        keyManagedExternally,
        hasKey: Boolean(envKey || conn.apiKeyEncrypted),
    };
}
/** Encrypt a plaintext key for storage and compute its masked suffix. */
export async function encryptProviderKey(plaintext) {
    const apiKeyEncrypted = await encryptSecret(plaintext);
    return { apiKeyEncrypted, apiKeyLast4: last4(plaintext) ?? '' };
}
/**
 * Read the AI config document. Returns a fully-populated object (defaults +
 * merged feature flags) even when no row exists yet, so callers never special-
 * case the empty state.
 */
export async function getAIConfig(db) {
    const d = db;
    const empty = { providers: [], settings: defaultAISettings() };
    if (!d?.document?.findFirst)
        return empty;
    try {
        const doc = await d.document.findFirst({
            where: { collection: AI_CONFIG_COLLECTION, deletedAt: null },
            select: { data: true },
        });
        if (!doc?.data || typeof doc.data !== 'object')
            return empty;
        const data = doc.data;
        const settings = (data.settings ?? {});
        return {
            providers: Array.isArray(data.providers) ? data.providers : [],
            settings: {
                ...defaultAISettings(),
                ...settings,
                features: mergeFeatures(settings.features),
                budget: { ...DEFAULT_AI_BUDGET, ...(settings.budget ?? {}) },
                logsRetentionDays: settings.logsRetentionDays ?? 30,
            },
            catalog: data.catalog,
            updatedAt: data.updatedAt,
            updatedBy: data.updatedBy,
        };
    }
    catch {
        return empty;
    }
}
/**
 * Upsert the AI config document. Caller is responsible for auth/role checks.
 * Stamps `updatedAt`/`updatedBy`.
 */
export async function putAIConfig(db, next, userId) {
    const d = db;
    const data = {
        ...next,
        updatedAt: new Date().toISOString(),
        updatedBy: userId,
    };
    const existing = await d.document.findFirst({
        where: { collection: AI_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: AI_CONFIG_COLLECTION,
                data,
                status: 'PUBLISHED',
                title: 'AI settings',
                slug: 'ai-config',
                createdById: userId,
                updatedById: userId,
            },
        });
    }
    return data;
}
/** Serialize a full config doc for the client (providers masked). */
export function toPublicConfig(doc) {
    return {
        providers: doc.providers.map(toPublicProvider),
        settings: doc.settings,
    };
}
//# sourceMappingURL=config-store.js.map