/**
 * Sensitive Action Confirm Middleware
 *
 * Protects high-risk endpoints (delete account, revoke all sessions, change password, etc.)
 * by requiring the caller to prove identity via one of two methods:
 *
 *   1. Password confirmation — `body.confirm_password` matches the user's stored password hash
 *   2. OTP confirmation     — `body.confirm_otp` is a valid, unexpired OTP stored in Redis
 *                             (placed there by `POST /auth/send-confirm-otp`)
 *
 * Usage in a route:
 *   middleware: [verify, requireSensitiveConfirm()]
 *
 * The middleware tries password first; if not supplied it tries OTP.
 * If neither is provided or valid it returns HTTP 403.
 */

import { NextFunction } from 'express';
import { timingSafeEqual } from 'crypto';
import { Req, Res } from '#interfaces/IApi';
import { AuthenticationError, Errors } from '#interfaces/error';
import { User } from '#models/User';
import { UserAuth } from '#models/UserAuth';
import { PasswordService } from '#services/auth/passwordService';
import RedisService from '#services/storage/redisService';

const redis = RedisService.getInstance();

/** Redis key for a pending confirm-OTP */
const confirmOtpKey = (userId: string) => `confirm_otp:${userId}`;

/**
 * Returns an Express middleware that enforces sensitive-action confirmation.
 * Call it in the middleware array of a route definition.
 */
export function requireSensitiveConfirm() {
	return async (req: Req, res: Res, next: NextFunction): Promise<void> => {
		const userId = req.user?.id;
		if (!userId) {
			res.error(new AuthenticationError('UNAUTHORIZED'));
			return;
		}

		const { confirm_password, confirm_otp } = req.body ?? {};

		// --- Method 1: password confirmation ---
		if (confirm_password) {
			try {
				const userWithAuth = await User.findByPk(userId, {
					include: [{ model: UserAuth, as: 'user_auth' }],
				});
				const passwordHash = (userWithAuth as any)?.user_auth?.password_hash;
				if (!passwordHash) {
					res.error(Errors.forbidden());
					return;
				}
				const valid = await PasswordService.verifyPassword(confirm_password, passwordHash);
				if (!valid) {
					res.error(Errors.invalidCredentials());
					return;
				}
				return next();
			} catch {
				res.error(Errors.internalError());
				return;
			}
		}

		// --- Method 2: OTP confirmation ---
		if (confirm_otp) {
			const stored = await redis.get<string>(confirmOtpKey(userId));
			if (!stored) {
				res.error(
					Errors.validation([
						{
							field: 'confirm_otp',
							code: 'INVALID_CODE',
							message: {
								vi: 'Mã OTP xác nhận không hợp lệ hoặc đã hết hạn',
								en: 'Invalid or expired confirmation OTP',
							},
						},
					]),
				);
				return;
			}
			// M-03: Use timing-safe comparison to prevent timing-based OTP oracle
			const a = Buffer.from(stored);
			const b = Buffer.from(String(confirm_otp));
			const isMatch = a.length === b.length && timingSafeEqual(a, b);
			if (!isMatch) {
				res.error(
					Errors.validation([
						{
							field: 'confirm_otp',
							code: 'INVALID_CODE',
							message: {
								vi: 'Mã OTP xác nhận không hợp lệ hoặc đã hết hạn',
								en: 'Invalid or expired confirmation OTP',
							},
						},
					]),
				);
				return;
			}
			// Single-use: delete after successful validation
			await redis.delete(confirmOtpKey(userId));
			return next();
		}

		res.error(
			Errors.validation([
				{
					field: 'confirm',
					code: 'VALIDATION_ERROR',
					message: {
						vi: 'Hành động này yêu cầu xác nhận mật khẩu (confirm_password) hoặc mã OTP (confirm_otp)',
						en: 'This action requires password confirmation (confirm_password) or OTP (confirm_otp)',
					},
				},
			]),
		);
	};
}

/**
 * Store a one-time confirm OTP in Redis for the given user.
 * TTL: 5 minutes.
 * Called by the `POST /auth/send-confirm-otp` endpoint.
 */
export async function storeConfirmOtp(userId: string, otp: string, ttlSeconds = 300): Promise<void> {
	await redis.set(confirmOtpKey(userId), otp, ttlSeconds);
}
