"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseTtl = void 0;
exports.hashPassword = hashPassword;
exports.verifyPassword = verifyPassword;
exports.signAccessToken = signAccessToken;
exports.signRefreshToken = signRefreshToken;
exports.signOAuthToken = signOAuthToken;
exports.signOAuthRefreshToken = signOAuthRefreshToken;
exports.verifyToken = verifyToken;
exports.setAuthCookies = setAuthCookies;
exports.clearAuthCookies = clearAuthCookies;
exports.getAccessToken = getAccessToken;
exports.getRefreshToken = getRefreshToken;
exports.resolveUserByEmail = resolveUserByEmail;
const crypto_1 = __importDefault(require("crypto"));
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
// ---------------------------------------------------------------------------
// Password hashing (scrypt)
// ---------------------------------------------------------------------------
const SCRYPT_KEYLEN = 64;
const SCRYPT_COST = 65536; // N
const SCRYPT_BLOCK = 8; // r
const SCRYPT_PARALLEL = 1; // p
const SCRYPT_MAXMEM = 128 * 1024 * 1024; // 128 MiB (needed for N=65536, r=8)
/**
 * Hash a password using scrypt. Returns a string: `$scrypt$N$r$p$salt$hash`
 */
async function hashPassword(password) {
    const salt = crypto_1.default.randomBytes(16).toString('hex');
    const derived = await new Promise((resolve, reject) => {
        crypto_1.default.scrypt(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_COST, r: SCRYPT_BLOCK, p: SCRYPT_PARALLEL, maxmem: SCRYPT_MAXMEM }, (err, key) => {
            if (err)
                reject(err);
            else
                resolve(key);
        });
    });
    return `$scrypt$${SCRYPT_COST}$${SCRYPT_BLOCK}$${SCRYPT_PARALLEL}$${salt}$${derived.toString('hex')}`;
}
/**
 * Verify a password against a stored hash.
 */
async function verifyPassword(password, storedHash) {
    const parts = storedHash.split('$');
    // Format: $scrypt$N$r$p$salt$hash → ['', 'scrypt', N, r, p, salt, hash]
    if (parts.length !== 7 || parts[1] !== 'scrypt')
        return false;
    const N = parseInt(parts[2], 10);
    const r = parseInt(parts[3], 10);
    const p = parseInt(parts[4], 10);
    const salt = parts[5];
    const expectedHash = parts[6];
    const derived = await new Promise((resolve, reject) => {
        crypto_1.default.scrypt(password, salt, SCRYPT_KEYLEN, { N, r, p, maxmem: SCRYPT_MAXMEM }, (err, key) => {
            if (err)
                reject(err);
            else
                resolve(key);
        });
    });
    const derivedHex = derived.toString('hex');
    // Timing-safe comparison
    if (derivedHex.length !== expectedHash.length)
        return false;
    return crypto_1.default.timingSafeEqual(Buffer.from(derivedHex), Buffer.from(expectedHash));
}
const duration_1 = require("../lib/duration");
/**
 * Parse a TTL string like "15m", "1h", "7d", "1d2h" into seconds.
 */
exports.parseTtl = duration_1.parseDuration;
function signAccessToken(userId, secret, ttl) {
    const payload = { userId, type: 'access' };
    return jsonwebtoken_1.default.sign(payload, secret, { algorithm: 'HS256', expiresIn: (0, exports.parseTtl)(ttl) });
}
function signRefreshToken(userId, secret, ttl) {
    const payload = { userId, type: 'refresh' };
    return jsonwebtoken_1.default.sign(payload, secret, { algorithm: 'HS256', expiresIn: (0, exports.parseTtl)(ttl) });
}
function signOAuthToken(userId, secret, ttl) {
    const payload = { userId, type: 'oauth_access' };
    return jsonwebtoken_1.default.sign(payload, secret, { algorithm: 'HS256', expiresIn: (0, exports.parseTtl)(ttl) });
}
function signOAuthRefreshToken(userId, secret, ttl) {
    const payload = { userId, type: 'oauth_refresh' };
    return jsonwebtoken_1.default.sign(payload, secret, { algorithm: 'HS256', expiresIn: (0, exports.parseTtl)(ttl) });
}
function verifyToken(token, secret) {
    try {
        const decoded = jsonwebtoken_1.default.verify(token, secret, { algorithms: ['HS256'] });
        if (!decoded.userId || !decoded.type)
            return null;
        return { userId: decoded.userId, type: decoded.type };
    }
    catch {
        return null;
    }
}
// ---------------------------------------------------------------------------
// Cookie helpers
// ---------------------------------------------------------------------------
const ACCESS_COOKIE = 'mgm_access';
const REFRESH_COOKIE = 'mgm_refresh';
function setAuthCookies(res, accessToken, refreshToken, refreshTtl, secureCookie) {
    const secure = secureCookie ?? process.env.NODE_ENV !== 'development';
    res.cookie(ACCESS_COOKIE, accessToken, {
        httpOnly: true,
        secure,
        sameSite: 'strict',
        path: '/api',
        maxAge: (0, exports.parseTtl)(refreshTtl) * 1000,
    });
    res.cookie(REFRESH_COOKIE, refreshToken, {
        httpOnly: true,
        secure,
        sameSite: 'strict',
        path: '/api/auth/refresh',
        maxAge: (0, exports.parseTtl)(refreshTtl) * 1000,
    });
}
function clearAuthCookies(res) {
    res.clearCookie(ACCESS_COOKIE, { path: '/api' });
    res.clearCookie(REFRESH_COOKIE, { path: '/api/auth/refresh' });
}
function getAccessToken(req) {
    return req.cookies?.[ACCESS_COOKIE];
}
function getRefreshToken(req) {
    return req.cookies?.[REFRESH_COOKIE];
}
// ---------------------------------------------------------------------------
// User lookup by email
// ---------------------------------------------------------------------------
function resolveUserByEmail(email, users) {
    for (const [userId, user] of Object.entries(users)) {
        if (user.email === email)
            return { userId, user };
    }
    return undefined;
}
