/**
 * Theme A.4c — /api/auth/login
 *
 * First hop of the 2FA-aware login flow. Verifies the password and either:
 *   - returns a pendingToken (when AUTH_2FA_ENFORCED and the user has 2FA),
 *   - or returns a post-2fa sessionToken the client immediately exchanges
 *     via signIn('credentials', { sessionToken }) to create a session.
 *
 * Existing signIn(username, password) path stays untouched until rollout —
 * the AUTH_2FA_ENFORCED flag defaults to false, which short-circuits the
 * 2FA branch.
 */
import { NextResponse } from 'next/server';
import bcrypt from 'bcryptjs';
import { prisma } from '@/lib/prisma';
import { env } from '@/lib/env';
import { isLocked, recordFailure, recordSuccess } from '@/lib/auth/login-attempts';
import { mintPendingLoginToken, mintPost2faToken } from '@/lib/auth/pending-login-token';
import { parseJsonBounded, BodyTooLargeError, BodyParseError } from '@/lib/api-helpers/parse-json';

export async function POST(request: Request) {
    try {
        // 64 KiB ceiling — usernames + passwords are short; anything
        // larger is hostile.
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const body = await parseJsonBounded<any>(request, { maxBytes: 64 * 1024 });
        const username = typeof body?.username === 'string' ? body.username.trim() : '';
        const password = typeof body?.password === 'string' ? body.password : '';

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

        const lock = await isLocked(username);
        if (lock.locked) {
            return NextResponse.json(
                {
                    error: 'locked',
                    message: 'Too many failed sign-in attempts. Try again later.',
                    retryAfterSeconds: lock.retryAfterSeconds,
                },
                { status: 423 }
            );
        }

        const user = await prisma.user.findUnique({ where: { email: username } });
        if (!user) {
            // Always count unknown-email failures to prevent enumeration via
            // lockout timing.
            await recordFailure(username);
            return NextResponse.json(
                { error: 'Invalid credentials' },
                { status: 401 }
            );
        }

        const ok = await bcrypt.compare(password, user.password);
        if (!ok) {
            await recordFailure(username);
            return NextResponse.json(
                { error: 'Invalid credentials' },
                { status: 401 }
            );
        }

        await recordSuccess(username);

        // 2FA gate: only when both the user is enrolled AND the flag is on.
        if (env.AUTH_2FA_ENFORCED && user.totpEnabled) {
            const pendingToken = mintPendingLoginToken(user.id);
            return NextResponse.json({ needs2FA: true, pendingToken }, { status: 200 });
        }

        // No 2FA needed: hand out a post-2fa session token the client
        // immediately exchanges via signIn() to create the NextAuth session.
        const sessionToken = mintPost2faToken(user.id);
        return NextResponse.json({ needs2FA: false, sessionToken }, { status: 200 });

    } 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('Login error:', error);
        return NextResponse.json(
            { error: 'Internal Server Error' },
            { status: 500 }
        );
    }
}
