/**
 * First-run setup utilities for Actuate CMS.
 *
 * Handles two setup paths:
 * 1. Interactive — setup wizard UI at /admin when no users exist
 * 2. Automated — env-var-based admin creation for CI/headless deploys
 */
import { hashPassword } from '../auth/password.js';
/**
 * Detect errors that mean the database is unreachable / not initialized,
 * as opposed to an empty-but-healthy database.
 *
 * We must NOT treat these as "needs setup": doing so makes a DB outage
 * silently render the first-run account-creation wizard, and the
 * subsequent create-admin write then fails with an opaque 400. Surfacing
 * the error instead lets callers return a proper 5xx and lets operators
 * see the real cause (e.g. `ECONNREFUSED`, Prisma `P1001`).
 */
export function isDatabaseUnavailableError(err) {
    if (!err || typeof err !== 'object')
        return false;
    const code = err.code;
    const name = err.name;
    // Prisma connection/init error codes: can't reach server, timeouts,
    // auth failures, db-does-not-exist, and the raw socket error.
    const connectionCodes = new Set([
        'P1000',
        'P1001',
        'P1002',
        'P1003',
        'P1008',
        'P1017',
        'ECONNREFUSED',
        'ETIMEDOUT',
        'ENOTFOUND',
    ]);
    if (typeof code === 'string' && connectionCodes.has(code))
        return true;
    if (name === 'PrismaClientInitializationError')
        return true;
    const message = err.message;
    if (typeof message === 'string') {
        return (message.includes('ECONNREFUSED') ||
            message.includes("Can't reach database server") ||
            message.includes('Authentication failed against database'));
    }
    return false;
}
/**
 * Detect errors that mean the database is reachable but the Actuate CMS tables
 * have not been created yet (migrations not applied). This is a distinct,
 * recoverable state from "empty database" — surfacing it lets the UI tell the
 * operator to run migrations instead of dropping them on the login screen or a
 * wizard that will fail on the first write.
 */
export function isSchemaMissingError(err) {
    if (!err || typeof err !== 'object')
        return false;
    const code = err.code;
    // Prisma P2021: table does not exist. P2022: column does not exist.
    if (code === 'P2021' || code === 'P2022')
        return true;
    const message = err.message;
    if (typeof message === 'string') {
        return (message.includes('does not exist in the current database') ||
            /relation "[^"]+" does not exist/i.test(message));
    }
    return false;
}
/**
 * Check if the CMS needs first-run setup (no admin users exist).
 * Uses a count query to avoid loading the full user model.
 *
 * A connection/initialization failure is rethrown so callers can report
 * it as an infrastructure error — only a successful query that returns
 * zero users counts as "setup required".
 */
export async function checkSetupRequired(db) {
    try {
        const userCount = await db.user.count();
        return { setupRequired: userCount === 0, userCount };
    }
    catch (err) {
        if (isDatabaseUnavailableError(err))
            throw err;
        // Reachable DB but no tables yet → operator must run migrations.
        if (isSchemaMissingError(err)) {
            return { setupRequired: false, userCount: 0, migrationsRequired: true };
        }
        return { setupRequired: true, userCount: 0 };
    }
}
/**
 * Create the initial admin user. Only succeeds if no users exist yet
 * (prevents race conditions and unauthorized account creation).
 */
export async function createInitialAdmin(db, input) {
    if (!input.email || !input.password || !input.name) {
        return { success: false, error: 'Email, password, and name are required' };
    }
    if (input.password.length < 12) {
        return { success: false, error: 'Password must be at least 12 characters' };
    }
    const passwordHash = await hashPassword(input.password);
    try {
        const user = await db.$transaction(async (tx) => {
            const existingCount = await tx.user.count();
            if (existingCount > 0) {
                throw new Error('Setup already completed — users exist');
            }
            return tx.user.create({
                data: {
                    email: input.email.toLowerCase().trim(),
                    name: input.name.trim(),
                    passwordHash,
                    role: 'ADMIN',
                    isActive: true,
                    isApproved: true,
                    emailVerified: true,
                },
            });
        });
        return { success: true, userId: user.id };
    }
    catch (err) {
        // A DB outage or an unmigrated schema is an infrastructure failure, not a
        // bad request — rethrow so the caller can return a 5xx with an actionable
        // message instead of a confusing 400.
        if (isDatabaseUnavailableError(err) || isSchemaMissingError(err))
            throw err;
        const message = err instanceof Error ? err.message : 'Setup failed';
        return { success: false, error: message };
    }
}
/**
 * Attempt to auto-create admin from environment variables.
 * Called during CMS bootstrap if CMS_ADMIN_EMAIL and CMS_ADMIN_PASSWORD are set.
 * Silently no-ops if users already exist.
 */
export async function autoSeedAdmin(db) {
    const email = process.env.CMS_ADMIN_EMAIL;
    const password = process.env.CMS_ADMIN_PASSWORD;
    if (!email || !password) {
        return null;
    }
    const { setupRequired } = await checkSetupRequired(db);
    if (!setupRequired) {
        return null;
    }
    const name = process.env.CMS_ADMIN_NAME ?? 'Admin';
    return createInitialAdmin(db, { email, password, name });
}
//# sourceMappingURL=index.js.map