import { randomBytes } from 'crypto';
import { Application } from 'express';
import { Resource } from 'express-automatic-routes';
import { UserProvider } from '#providers/UserProvider';
import { PasswordResetTokenProvider } from '#providers/PasswordResetTokenProvider';
import { Req, Res } from '#interfaces/IApi';
import { validateZod } from '#middlewares/validators';
import { PasswordResetRequestSchema } from '#contracts/auth/schema';
import { authRateLimit } from '#middlewares/rate-limiter';
import { PasswordService } from '#services/auth';
import MailService from '#services/notification/notificationEmailService';
import { getPasswordResetOTPEmail } from '#templates/email/passwordResetOTP';
import LoggingService from '#services/file-system/logService';
import { sendSuccess } from '#utils/responseUtils';
import Config from '#config';

export default (_express: Application) => {
	const userProvider = new UserProvider();
	const codeProvider = new PasswordResetTokenProvider();
	const mailService = MailService.getInstance();
	const logger = new LoggingService();

	return <Resource>{
		post: {
			middleware: [authRateLimit, validateZod(PasswordResetRequestSchema)] as any,
			handler: async (req: Req, res: Res) => {
				const transaction = await userProvider.transaction();

				// Generic response to prevent account enumeration
				const GENERIC_RESPONSE = { sent: true, expiresIn: 10 };

				try {
					const { email } = req.body;

					const users = await userProvider.getAll({
						where: { email: email },
					});

					// LOGIC-01: Silently succeed if email not found - prevents account enumeration
					// Roll back transaction before returning response
					if (!users.rows.length) {
						await transaction.rollback();
						return sendSuccess(
							res,
							GENERIC_RESPONSE,
							'Nếu email tồn tại, bạn sẽ nhận được mã xác thực',
							'If this email exists, you will receive a reset code',
						);
					}

					const user = users.rows[0]!;
					// SEC-01: Use cryptographically secure random for OTP generation
					const code = ((randomBytes(3).readUInt32BE(0) % 900000) + 100000).toString();
					const tokenHash = await PasswordService.hashPassword(code);

					const expiresAt = new Date();
					expiresAt.setMinutes(expiresAt.getMinutes() + 10);

					await codeProvider.create(
						{
							id: crypto.randomUUID(),
							user_id: user.id,
							token_hash: tokenHash,
							expires_at: expiresAt,
							created_at: new Date(),
						},
						{ transaction },
					);

					// Commit transaction before sending email so OTP is always persisted
					await transaction.commit();

					// Send email fire-and-forget — failure does not affect the response
					mailService
						.sendmail({
							from: Config.email.from,
							to: user.email,
							subject: 'Mã đặt lại mật khẩu',
							html: getPasswordResetOTPEmail({
								username: user.username || '',
								email: user.email,
								code,
								expiresInMinutes: 10,
							}),
						})
						.catch((error) => {
							logger
								.logErrorAsync('ForgotPassword', error instanceof Error ? error : new Error(String(error)), null)
								.catch(() => {});
						});

					return sendSuccess(res, GENERIC_RESPONSE);
				} catch (error) {
					await transaction.rollback();
					return res.error(error);
				}
			},
		},
	};
};
