/**
 * Theme A.3 — short-lived HMAC-signed login tokens.
 *
 * Two `kind`s share the same primitive:
 *
 *   "pending-login"   issued after password verification, BEFORE 2FA.
 *                     Carried to /api/auth/2fa-challenge so userId is
 *                     authenticated by signature, not body input.
 *                     TTL: 5 minutes.
 *
 *   "post-2fa"        issued after successful 2FA challenge. The client
 *                     immediately passes it to signIn() so the
 *                     credentials provider can skip password + TOTP and
 *                     create the session. TTL: 60 seconds — long enough
 *                     for a single signIn round-trip, short enough to
 *                     make a stolen token nearly useless.
 *
 * The `kind` field is checked on verify, so a pending-login token
 * cannot be replayed as a post-2fa token (and vice versa). Distinct
 * kinds get distinct domain logic without two near-duplicate modules.
 *
 * Stateless: signature + expiry are sufficient; no DB row needed.
 * Format: `<base64url-payload>.<base64url-signature>`.
 */
import crypto from 'crypto';
import { env } from '@/lib/env';

export type TokenKind = 'pending-login' | 'post-2fa';

const TTL_SECONDS: Record<TokenKind, number> = {
    'pending-login': 5 * 60,
    'post-2fa': 60,
};

const ALG = 'sha256';

interface TokenPayload {
    userId: number;
    kind: TokenKind;
    iat: number;
    exp: number;
}

function b64urlEncode(input: string | Buffer): string {
    return Buffer.from(input).toString('base64url');
}

function b64urlDecode(input: string): Buffer {
    return Buffer.from(input, 'base64url');
}

function sign(payload: string): string {
    return crypto
        .createHmac(ALG, env.NEXTAUTH_SECRET)
        .update(payload)
        .digest('base64url');
}

function mint(userId: number, kind: TokenKind): string {
    const now = Math.floor(Date.now() / 1000);
    const payload: TokenPayload = {
        userId,
        kind,
        iat: now,
        exp: now + TTL_SECONDS[kind],
    };
    const encoded = b64urlEncode(JSON.stringify(payload));
    const sig = sign(encoded);
    return `${encoded}.${sig}`;
}

function verify(token: string, expectedKind: TokenKind): { userId: number } | null {
    if (typeof token !== 'string' || !token) return null;
    const parts = token.split('.');
    if (parts.length !== 2) return null;
    const [encoded, sig] = parts;

    const expected = sign(encoded);
    let expectedBuf: Buffer;
    let actualBuf: Buffer;
    try {
        expectedBuf = b64urlDecode(expected);
        actualBuf = b64urlDecode(sig);
    } catch {
        return null;
    }
    if (expectedBuf.length !== actualBuf.length) return null;
    if (!crypto.timingSafeEqual(expectedBuf, actualBuf)) return null;

    let payload: TokenPayload;
    try {
        payload = JSON.parse(b64urlDecode(encoded).toString('utf8'));
    } catch {
        return null;
    }

    if (
        typeof payload?.userId !== 'number' ||
        typeof payload?.exp !== 'number' ||
        payload.kind !== expectedKind
    ) {
        return null;
    }
    const now = Math.floor(Date.now() / 1000);
    if (payload.exp <= now) return null;

    return { userId: payload.userId };
}

export function mintPendingLoginToken(userId: number): string {
    return mint(userId, 'pending-login');
}
export function verifyPendingLoginToken(token: string): { userId: number } | null {
    return verify(token, 'pending-login');
}

export function mintPost2faToken(userId: number): string {
    return mint(userId, 'post-2fa');
}
export function verifyPost2faToken(token: string): { userId: number } | null {
    return verify(token, 'post-2fa');
}
