/**
 * Theme A.4d — /api/auth/2fa-challenge
 *
 * Second hop of the 2FA-aware login flow:
 *   POST { pendingToken, code }
 *   - Verify pendingToken signature -> userId (signature-authenticated;
 *     body never trusted to name the user).
 *   - Per-(userId, ip) rate limit via twofa-attempts.
 *   - Try TOTP code first, then backup code.
 *   - On success, mint a post-2fa sessionToken that the client
 *     immediately exchanges via signIn() to establish the session.
 */
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { TwoFactorService } from '@/lib/services/two-factor.service';
import { isLocked, recordFailure, recordSuccess } from '@/lib/auth/twofa-attempts';
import {
    verifyPendingLoginToken,
    mintPost2faToken,
} from '@/lib/auth/pending-login-token';
import { parseJsonBounded, BodyTooLargeError, BodyParseError } from '@/lib/api-helpers/parse-json';

export async function POST(request: Request) {
    // AUDIT-1 (2026-05-23): per-IP attempt tracking goes through the
    // trusted-ip helper. Without TRUSTED_PROXY_CIDRS set, every caller
    // gets the same 'direct-mode' key, so per-IP throttling collapses
    // to a global bucket. The actual brute-force defense for 2FA is
    // per-user (`twofa-attempts.ts` keys on the pending-login userId
    // inside the verified token).
    const { getRateLimitKeyFromHeaders } = await import('@/lib/network-security/trusted-ip');
    const ip = getRateLimitKeyFromHeaders(request.headers);

    try {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const body = await parseJsonBounded<any>(request, { maxBytes: 64 * 1024 });
        const pendingToken: string | undefined =
            typeof body?.pendingToken === 'string' ? body.pendingToken : undefined;
        const code: string | undefined =
            typeof body?.code === 'string' ? body.code : undefined;

        if (!pendingToken || !code) {
            return NextResponse.json(
                { error: 'pendingToken and code are required' },
                { status: 400 }
            );
        }

        // userId comes from the verified signature, never from the body.
        const verified = verifyPendingLoginToken(pendingToken);
        if (!verified) {
            return NextResponse.json(
                { error: 'Invalid or expired pending-login token' },
                { status: 401 }
            );
        }
        const userId = verified.userId;

        const lock = await isLocked(userId, ip);
        if (lock.locked) {
            return NextResponse.json(
                {
                    error: 'locked',
                    message: 'Too many failed 2FA attempts. Try again later.',
                    retryAfterSeconds: lock.retryAfterSeconds,
                },
                { status: 423 }
            );
        }

        const user = await prisma.user.findUnique({
            where: { id: userId },
            select: { totpEnabled: true, totpSecret: true },
        });

        if (!user || !user.totpEnabled || !user.totpSecret) {
            await recordFailure(userId, ip);
            return NextResponse.json(
                { error: 'Invalid verification code' },
                { status: 401 }
            );
        }

        if (!/^[0-9A-Za-z-]{6,14}$/.test(code)) {
            await recordFailure(userId, ip);
            return NextResponse.json(
                { error: 'Invalid verification code' },
                { status: 401 }
            );
        }

        // Try TOTP first.
        // Auditor T1C: pass userId so decrypt verifies AAD = user:<id>:totp.
        if (TwoFactorService.verifyCode(user.totpSecret, code, userId)) {
            await recordSuccess(userId, ip);
            return NextResponse.json({
                sessionToken: mintPost2faToken(userId),
            });
        }

        // Then backup code (each one is single-use; verifyBackupCode handles consumption).
        const usedBackup = await TwoFactorService.verifyBackupCode(userId, code);
        if (usedBackup) {
            await recordSuccess(userId, ip);
            return NextResponse.json({
                sessionToken: mintPost2faToken(userId),
                usedBackupCode: true,
            });
        }

        await recordFailure(userId, ip);
        return NextResponse.json(
            { error: 'Invalid verification code' },
            { status: 401 }
        );

    } catch (error: unknown) {
        if (error instanceof BodyTooLargeError) {
            return NextResponse.json({ error: error.message }, { status: 413 });
        }
        if (error instanceof BodyParseError) {
            return NextResponse.json({ error: error.message }, { status: 400 });
        }
        console.error('2FA challenge error:', error);
        return NextResponse.json(
            { error: 'Internal Server Error' },
            { status: 500 }
        );
    }
}
