import { getPrismaClient } from '../db/prisma.js';
import { createLogger } from '@4runr/shared';
import { authenticator } from 'otplib';
import QRCode from 'qrcode';
import { z } from 'zod';
import { sealMfaSecret, openMfaSecret, sealBackupCodes } from '../crypto/mfa-storage.js';
import { requireAuth } from '../middleware/auth.js';
import { writeRateLimit } from '../middleware/rateLimit.js';
const logger = createLogger('Gateway:Routes:MFA');
const setupMFASchema = z.object({
    userId: z.string().uuid(),
});
const verifyMFASchema = z.object({
    userId: z.string().uuid(),
    token: z.string().length(6),
});
const enableMFASchema = z.object({
    userId: z.string().uuid(),
    token: z.string().length(6),
});
function assertMfaSubject(request, userId, reply) {
    const auth = request.auth ||
        request.user;
    if (auth?.sub && auth.sub !== userId) {
        reply.status(403).send({
            error: 'forbidden',
            message: 'userId does not match authenticated identity',
        });
        return false;
    }
    return true;
}
export async function mfaRoutes(fastify) {
    logger.info('Registering MFA routes...');
    fastify.post('/api/mfa/setup', {
        preHandler: [requireAuth, writeRateLimit],
    }, async (request, reply) => {
        try {
            const body = setupMFASchema.parse(request.body);
            if (!assertMfaSubject(request, body.userId, reply))
                return;
            const prisma = await getPrismaClient();
            const user = await prisma.user.findUnique({
                where: { id: body.userId },
            });
            if (!user) {
                return reply.status(404).send({
                    error: 'not_found',
                    message: 'User not found',
                });
            }
            const secret = authenticator.generateSecret();
            const serviceName = process.env['MFA_SERVICE_NAME'] || '4Runr';
            const accountName = user.email;
            const otpauthUrl = authenticator.keyuri(accountName, serviceName, secret);
            const qrCodeDataUrl = await QRCode.toDataURL(otpauthUrl);
            const storedSecret = sealMfaSecret(secret);
            await prisma.user.update({
                where: { id: body.userId },
                data: {
                    mfaSecret: storedSecret,
                },
            });
            return reply.send({
                secret,
                qrCode: qrCodeDataUrl,
                otpauthUrl,
                message: 'Verify token before enabling MFA',
            });
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            logger.error('Error setting up MFA', { error: msg });
            if (error instanceof z.ZodError) {
                return reply.status(400).send({
                    error: 'invalid_request',
                    message: 'Invalid request body',
                    details: error.errors,
                });
            }
            return reply.status(500).send({
                error: 'internal_error',
                message: 'Failed to setup MFA',
            });
        }
    });
    fastify.post('/api/mfa/verify', {
        preHandler: [requireAuth, writeRateLimit],
    }, async (request, reply) => {
        try {
            const body = verifyMFASchema.parse(request.body);
            if (!assertMfaSubject(request, body.userId, reply))
                return;
            const prisma = await getPrismaClient();
            const user = await prisma.user.findUnique({
                where: { id: body.userId },
            });
            if (!user || !user.mfaSecret) {
                return reply.status(400).send({
                    error: 'invalid_request',
                    message: 'MFA not set up. Call /api/mfa/setup first',
                });
            }
            const secret = openMfaSecret(user.mfaSecret);
            if (!secret) {
                return reply.status(400).send({
                    error: 'invalid_request',
                    message: 'MFA not set up. Call /api/mfa/setup first',
                });
            }
            const isValid = authenticator.verify({ token: body.token, secret });
            if (!isValid) {
                return reply.status(403).send({
                    error: 'mfa_invalid',
                    message: 'Invalid MFA token',
                });
            }
            return reply.send({
                verified: true,
                message: 'Token verified. Call /api/mfa/enable to activate MFA',
            });
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            logger.error('Error verifying MFA', { error: msg });
            if (error instanceof z.ZodError) {
                return reply.status(400).send({
                    error: 'invalid_request',
                    message: 'Invalid request body',
                    details: error.errors,
                });
            }
            return reply.status(500).send({
                error: 'internal_error',
                message: 'Failed to verify MFA',
            });
        }
    });
    fastify.post('/api/mfa/enable', {
        preHandler: [requireAuth, writeRateLimit],
    }, async (request, reply) => {
        try {
            const body = enableMFASchema.parse(request.body);
            if (!assertMfaSubject(request, body.userId, reply))
                return;
            const prisma = await getPrismaClient();
            const user = await prisma.user.findUnique({
                where: { id: body.userId },
            });
            if (!user || !user.mfaSecret) {
                return reply.status(400).send({
                    error: 'invalid_request',
                    message: 'MFA not set up. Call /api/mfa/setup first',
                });
            }
            const secret = openMfaSecret(user.mfaSecret);
            if (!secret) {
                return reply.status(400).send({
                    error: 'invalid_request',
                    message: 'MFA not set up. Call /api/mfa/setup first',
                });
            }
            const isValid = authenticator.verify({ token: body.token, secret });
            if (!isValid) {
                return reply.status(403).send({
                    error: 'mfa_invalid',
                    message: 'Invalid MFA token',
                });
            }
            const backupCodes = [];
            for (let i = 0; i < 10; i++) {
                backupCodes.push(Math.random().toString(36).substring(2, 10).toUpperCase());
            }
            const storedCodes = sealBackupCodes(backupCodes);
            await prisma.user.update({
                where: { id: body.userId },
                data: {
                    mfaEnabled: true,
                    mfaBackupCodes: storedCodes,
                    mfaVerifiedAt: new Date(),
                },
            });
            return reply.send({
                enabled: true,
                backupCodes,
                message: 'MFA enabled successfully. Save your backup codes!',
            });
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            logger.error('Error enabling MFA', { error: msg });
            if (error instanceof z.ZodError) {
                return reply.status(400).send({
                    error: 'invalid_request',
                    message: 'Invalid request body',
                    details: error.errors,
                });
            }
            return reply.status(500).send({
                error: 'internal_error',
                message: 'Failed to enable MFA',
            });
        }
    });
    fastify.post('/api/mfa/disable', {
        preHandler: [requireAuth, writeRateLimit],
    }, async (request, reply) => {
        try {
            const body = z.object({ userId: z.string().uuid() }).parse(request.body);
            if (!assertMfaSubject(request, body.userId, reply))
                return;
            const prisma = await getPrismaClient();
            await prisma.user.update({
                where: { id: body.userId },
                data: {
                    mfaEnabled: false,
                    mfaSecret: null,
                    mfaBackupCodes: [],
                    mfaVerifiedAt: null,
                },
            });
            return reply.send({
                disabled: true,
                message: 'MFA disabled successfully',
            });
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            logger.error('Error disabling MFA', { error: msg });
            return reply.status(500).send({
                error: 'internal_error',
                message: 'Failed to disable MFA',
            });
        }
    });
    logger.info('MFA routes registration completed', {
        routes: ['/api/mfa/setup', '/api/mfa/verify', '/api/mfa/enable', '/api/mfa/disable'],
    });
}
//# sourceMappingURL=mfa.js.map