import { checkSetupRequired, createInitialAdmin, isSchemaMissingError } from '../../setup/index.js';
import { logEvent } from '../../security/audit.js';
import { getCaptchaConfig } from '../../security/captcha.js';
import { getUpdateStatus } from '../../upgrade/version-check.js';
import { createUpgradePR } from '../../upgrade/upgrade-pr.js';
import { encryptField, decryptField } from '../../security/encrypted-fields.js';
import { getActuateConfig, getActuateCoreVersion } from '../../config/runtime.js';
import { validateEnvShape } from '../../diagnostics/env.js';
import { guardedDb } from '../guarded-db.js';
import { json, errorResponse, internalError, isSecretMissing, extractSession, requireAdminScope, loginLimiter, checkRateLimitAsync, } from '../route-helpers.js';
export function registerSystemRoutes(router) {
    const db = guardedDb;
    // ---------------------------------------------------------------------------
    // Setup routes
    // ---------------------------------------------------------------------------
    // ---------------------------------------------------------------------------
    // Update routes
    // ---------------------------------------------------------------------------
    const UPDATE_CONFIG_KEY = '_cms_update_config';
    async function getUpdateConfig() {
        const encKey = process.env.CMS_ENCRYPTION_KEY;
        if (!encKey)
            return {};
        try {
            const doc = await db().document.findFirst({
                where: { collection: UPDATE_CONFIG_KEY, deletedAt: null },
            });
            if (!doc?.data)
                return {};
            const raw = doc.data;
            const result = {};
            if (raw.githubToken)
                result.githubToken = await decryptField(raw.githubToken, encKey);
            if (raw.githubRepo)
                result.githubRepo = await decryptField(raw.githubRepo, encKey);
            return result;
        }
        catch {
            return {};
        }
    }
    router.get('/updates/check', async (request) => {
        try {
            const session = await extractSession(request);
            if (!session)
                return errorResponse('Unauthorized', 401);
            const coreVersion = getActuateCoreVersion() ?? '0.1.0';
            const info = await getUpdateStatus(coreVersion);
            // GitHub integration config is only relevant to (and only exposed to)
            // admins — the repo name shouldn't leak to non-admin sessions. The
            // version/changelog payload itself is safe for any authenticated user.
            const isAdmin = requireAdminScope(session) === null;
            if (!isAdmin) {
                return json({ data: { ...info, canManage: false } });
            }
            const saved = await getUpdateConfig();
            return json({
                data: {
                    ...info,
                    canManage: true,
                    hasGithubToken: !!(saved.githubToken || process.env.ACTUATE_GITHUB_TOKEN),
                    githubRepo: saved.githubRepo || process.env.ACTUATE_GITHUB_REPO || '',
                },
            });
        }
        catch (err) {
            return internalError(err);
        }
    });
    router.get('/updates/config', async (request) => {
        try {
            const session = await extractSession(request);
            if (!session)
                return errorResponse('Unauthorized', 401);
            const adminErr = requireAdminScope(session);
            if (adminErr)
                return adminErr;
            const saved = await getUpdateConfig();
            return json({
                data: {
                    hasGithubToken: !!(saved.githubToken || process.env.ACTUATE_GITHUB_TOKEN),
                    githubRepo: saved.githubRepo || process.env.ACTUATE_GITHUB_REPO || '',
                },
            });
        }
        catch (err) {
            return internalError(err);
        }
    });
    router.put('/updates/config', async (request) => {
        try {
            const session = await extractSession(request);
            if (!session)
                return errorResponse('Unauthorized', 401);
            const adminErr = requireAdminScope(session);
            if (adminErr)
                return adminErr;
            const encKey = process.env.CMS_ENCRYPTION_KEY;
            if (!encKey) {
                return errorResponse('CMS_ENCRYPTION_KEY is required to store encrypted credentials.', 400);
            }
            const body = (await request.json());
            if (body.githubRepo && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(body.githubRepo)) {
                return errorResponse('Invalid repository format. Use owner/repo.', 400);
            }
            const encrypted = {};
            if (body.githubToken)
                encrypted.githubToken = await encryptField(body.githubToken, encKey);
            if (body.githubRepo)
                encrypted.githubRepo = await encryptField(body.githubRepo, encKey);
            await db().document.upsert({
                where: { collection_slug: { collection: UPDATE_CONFIG_KEY, slug: UPDATE_CONFIG_KEY } },
                create: {
                    collection: UPDATE_CONFIG_KEY,
                    slug: UPDATE_CONFIG_KEY,
                    title: 'Update Configuration',
                    data: encrypted,
                    status: 'PUBLISHED',
                    createdById: session.userId,
                    updatedById: session.userId,
                },
                update: {
                    data: encrypted,
                    updatedById: session.userId,
                },
            });
            await logEvent({
                event: 'settings_changed',
                userId: session.userId,
                details: { setting: 'update_config' },
            });
            return json({ data: { success: true } });
        }
        catch (err) {
            return internalError(err);
        }
    });
    router.post('/updates/apply', async (request) => {
        try {
            const session = await extractSession(request);
            if (!session)
                return errorResponse('Unauthorized', 401);
            const adminErr = requireAdminScope(session);
            if (adminErr)
                return adminErr;
            const body = (await request.json());
            if (!body.targetVersion) {
                return errorResponse('targetVersion is required', 400);
            }
            if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(body.targetVersion)) {
                return errorResponse('Invalid version format', 400);
            }
            const saved = await getUpdateConfig();
            const githubToken = saved.githubToken || process.env.ACTUATE_GITHUB_TOKEN;
            const githubRepo = saved.githubRepo || process.env.ACTUATE_GITHUB_REPO;
            const owner = githubRepo?.split('/')[0];
            const repo = githubRepo?.split('/')[1];
            if (!githubToken) {
                return errorResponse('GitHub token not configured. Go to Settings > Updates to add one.', 400);
            }
            if (!owner || !repo) {
                return errorResponse('GitHub repository not configured. Go to Settings > Updates to add one.', 400);
            }
            const coreVersion = getActuateCoreVersion() ?? '0.1.0';
            const result = await createUpgradePR({
                owner,
                repo,
                targetVersion: body.targetVersion,
                changesDescription: `Upgrade Actuate CMS from ${coreVersion} to ${body.targetVersion}.\n\nRun \`npx prisma migrate deploy\` after merging.`,
                githubToken,
            });
            await logEvent({
                event: 'cms_update_initiated',
                userId: session.userId,
                details: {
                    fromVersion: coreVersion,
                    toVersion: body.targetVersion,
                    prUrl: result.prUrl,
                },
            });
            return json({ data: result });
        }
        catch (err) {
            return internalError(err);
        }
    });
    router.get('/captcha/config', async () => {
        const captchaConfig = getCaptchaConfig();
        return json({
            data: {
                provider: captchaConfig.provider,
                siteKey: captchaConfig.provider !== 'none' ? captchaConfig.siteKey : null,
            },
        });
    });
    router.get('/setup/status', async () => {
        try {
            const status = await checkSetupRequired(db());
            return json({ data: status });
        }
        catch (err) {
            return internalError(err);
        }
    });
    router.post('/setup/create-admin', async (request) => {
        try {
            const clientIp = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown';
            if (!(await checkRateLimitAsync(loginLimiter, `setup:${clientIp}`))) {
                return errorResponse('Too many setup attempts', 429);
            }
            const body = (await request.json());
            if (!body.name || !body.email || !body.password) {
                return errorResponse('Name, email, and password are required', 400);
            }
            const result = await createInitialAdmin(db(), {
                name: body.name,
                email: body.email,
                password: body.password,
            });
            if (!result.success) {
                return errorResponse(result.error ?? 'Setup failed', 400);
            }
            return json({ data: result }, 201);
        }
        catch (err) {
            if (isSchemaMissingError(err)) {
                return errorResponse('Database not migrated: the Actuate CMS tables do not exist yet. Run `prisma migrate deploy` against your database, then try again.', 503);
            }
            return internalError(err);
        }
    });
    // ---------------------------------------------------------------------------
    // Stats route
    // ---------------------------------------------------------------------------
    // ---------------------------------------------------------------------------
    // Health endpoint -- reports available models and CMS version
    // ---------------------------------------------------------------------------
    const CMS_EXPECTED_MODELS = [
        'document',
        'media',
        'user',
        'session',
        'version',
        'folder',
        'redirect',
        'formSubmission',
        'auditLog',
        'webhookEndpoint',
        'webhookDeliveryLog',
    ];
    router.get('/health', async () => {
        const cmsVersion = getActuateCoreVersion() ?? '0.0.0';
        const models = {};
        // M6: validate the *shape* of every well-known env var, not just presence.
        // A 31-char CMS_SECRET or a placeholder CMS_ENCRYPTION_KEY now reports as
        // a hard error here instead of crashing at the first runtime use.
        //
        // Bugbot review (PR #43): CMS_SECRET has *three* legitimate sources —
        // `process.env.CMS_SECRET`, the legacy `process.env.CMS_SESSION_SECRET`,
        // and `getActuateConfig()?.secret` (config-file path documented in the
        // missing-secret warning message itself). `getSessionSecret()` checks all
        // three; `isSecretMissing()` consequently returns false when any of them
        // is set. If `validateEnvShape()` only inspected `process.env.CMS_SECRET`,
        // a config-file deploy would get `secretConfigured: true` AND
        // `status: "unhealthy"` simultaneously — a contradiction that would trip
        // monitoring / load-balancer health probes for no real fault. We pass a
        // wrapped `EnvSource` so the validator sees what the runtime would
        // actually resolve.
        const env = validateEnvShape({
            get(name) {
                if (name === 'CMS_SECRET') {
                    return (process.env.CMS_SECRET ?? process.env.CMS_SESSION_SECRET ?? getActuateConfig()?.secret);
                }
                return process.env[name];
            },
        });
        // Derive overall status from env validation + model availability + DB
        // connection. `env.errorCount > 0` outranks every model/DB issue because
        // it represents a *deployment* misconfig the operator must fix before any
        // request will succeed — pretending the deploy is "degraded" when secrets
        // are malformed defeats the M6 goal of catching that at /health time.
        function deriveStatus(dbConnected, allModelsAvailable) {
            if (env.errorCount > 0)
                return 'unhealthy';
            if (allModelsAvailable && dbConnected)
                return 'healthy';
            return 'degraded';
        }
        let d;
        try {
            d = db();
        }
        catch {
            for (const m of CMS_EXPECTED_MODELS)
                models[m] = false;
            return json({
                data: {
                    status: deriveStatus(false, false),
                    version: cmsVersion,
                    secretConfigured: !isSecretMissing(),
                    env,
                    models,
                    databaseConnected: false,
                },
            });
        }
        let dbConnected = true;
        for (const modelName of CMS_EXPECTED_MODELS) {
            try {
                const model = d[modelName];
                if (model && typeof model.count === 'function') {
                    await model.count({ take: 0 });
                    models[modelName] = true;
                }
                else {
                    models[modelName] = false;
                }
            }
            catch {
                models[modelName] = false;
                if (modelName === CMS_EXPECTED_MODELS[0]) {
                    dbConnected = false;
                }
            }
        }
        const allAvailable = Object.values(models).every(Boolean);
        const status = deriveStatus(dbConnected, allAvailable);
        return json({
            data: {
                status,
                version: cmsVersion,
                secretConfigured: !isSecretMissing(),
                env,
                models,
                databaseConnected: dbConnected,
            },
        });
    });
}
//# sourceMappingURL=system.js.map