import bcrypt from "bcryptjs";
import crypto from "crypto";
import db from "../models/index.js";
import { sendOtpEmail, sendFailedOtpAttemptEmail } from "./emailService.js";

const { Otp, Merchant, Admin } = db;

const MAX_OTP_ATTEMPTS = 5;
const OTP_EXPIRES_MINUTES = parseInt(process.env.OTP_EXPIRES_MINUTES || "10");

/**
 * Generate a cryptographically secure 6-digit OTP.
 */
const generateOtpCode = () => {
  return String(crypto.randomInt(100000, 999999));
};

/**
 * Issue a new OTP for a user, send it via email, and return the OTP record.
 * Invalidates all previous unused OTPs for this user and purpose.
 *
 * @param {string} userId
 * @param {string} userType - 'admin' | 'merchant'
 * @param {string} purpose - 'login' | 'payout' | 'password_reset'
 * @param {string} email - Email address to send the OTP to
 * @param {string} name - Recipient name for the email
 * @returns {Promise<{otpId: string}>}
 */
export const issueOtp = async (userId, userType, purpose, email, name) => {
  // Invalidate all previous active OTPs for this user and purpose
  await Otp.update(
    { isUsed: true },
    { where: { userId, purpose, isUsed: false } }
  );

  const rawOtp = generateOtpCode();
  const hashedOtp = await bcrypt.hash(rawOtp, 10);
  const expiresAt = new Date(Date.now() + OTP_EXPIRES_MINUTES * 60 * 1000);

  const otpRecord = await Otp.create({
    userId,
    userType,
    otp: hashedOtp,
    purpose,
    expiresAt,
    isUsed: false,
    attempts: 0,
  });

  await sendOtpEmail(email, rawOtp, purpose, name);

  return { otpId: otpRecord.id };
};

/**
 * Verify an OTP submission.
 *
 * @param {string} otpId - The OTP record ID
 * @param {string} code - The plain text code submitted by the user
 * @returns {Promise<{success: boolean, message: string, data?: object}>}
 */
export const verifyOtp = async (otpId, code) => {
  const record = await Otp.findByPk(otpId);

  if (!record) {
    return { success: false, message: "Invalid or expired OTP session" };
  }

  if (record.isUsed) {
    return { success: false, message: "This OTP has already been used" };
  }

  if (new Date() > new Date(record.expiresAt)) {
    await record.update({ isUsed: true });
    return { success: false, message: "OTP has expired. Please request a new one." };
  }

  if (record.attempts >= MAX_OTP_ATTEMPTS) {
    await record.update({ isUsed: true });
    return { success: false, message: "Too many failed attempts. Please request a new OTP." };
  }

  const isMatch = await bcrypt.compare(code, record.otp);

  if (!isMatch) {
    await record.increment("attempts");
    const remainingAttempts = MAX_OTP_ATTEMPTS - (record.attempts + 1);

    if (remainingAttempts <= 0) {
      // Fetch user to get email and name for the alert
      let user = null;
      let name = "";
      let email = "";
      if (record.userType === "merchant") {
        user = await Merchant.findByPk(record.userId);
        if (user) {
          name = user.businessName;
          email = user.email;
        }
      } else if (record.userType === "admin") {
        user = await Admin.findByPk(record.userId);
        if (user) {
          name = user.fullName;
          email = user.email;
        }
      }

      if (email && name) {
        sendFailedOtpAttemptEmail(email, name).catch(() => {});
      }
    }

    return {
      success: false,
      message: `Invalid OTP. ${remainingAttempts > 0 ? `${remainingAttempts} attempts remaining.` : "No more attempts. Please request a new OTP."}`,
    };
  }

  // Mark OTP as consumed
  await record.update({ isUsed: true });

  return { success: true, message: "OTP verified successfully", data: record };
};
