import { Request, Response } from "express";
import { isValidEmail } from "../utils/helperFunctions";
import User from "../models/user";
import { otpStore } from "../database/otp-store";
import { sendEmail } from "../utils/mailer";
const otpGenerator = require("otp-generator");
import bcrypt from "bcrypt";

// A 4-digit OTP only has 10,000 possibilities — without a cap on attempts,
// it's brute-forceable well within its validity window. 5 wrong guesses
// burns the code and forces a fresh one to be requested.
const MAX_OTP_ATTEMPTS = 5;

function attemptsKey(userId: string) {
  return `otp-attempts-${userId}`;
}

// Returns null if the OTP is valid, or a {status, message} error to send
// if it isn't (wrong code, too many attempts, or no code on file).
async function checkOtp(
  userId: string,
  otp: string,
): Promise<{ status: number; message: string } | null> {
  const attempts = parseInt(otpStore.get(attemptsKey(userId)) || "0", 10);
  if (attempts >= MAX_OTP_ATTEMPTS) {
    otpStore.del(`otp-${userId}`);
    otpStore.del(attemptsKey(userId));
    return {
      status: 429,
      message: "Too many incorrect attempts. Please request a new code.",
    };
  }

  const storedOTP = otpStore.get(`otp-${userId}`);
  if (!storedOTP || storedOTP !== otp) {
    otpStore.setEx(attemptsKey(userId), 696, String(attempts + 1));
    return { status: 400, message: "Invalid OTP." };
  }

  return null;
}

class ForgotPasswordController {
  static async Execute(req: Request, res: Response) {
    const { email } = req.body;

    if (typeof email !== "string" || !isValidEmail(email)) {
      return res.status(400).send({
        message: "Email is required.",
      });
    }

    const user = await User.findOne({ email });

    if (!user || Object.keys(user).length == 0) {
      return res.status(400).send({
        message: "User not found.",
      });
    }

    const otp = otpGenerator.generate(4, {
      upperCaseAlphabets: false,
      specialChars: false,
      lowerCaseAlphabets: false,
    });

    otpStore.setEx(`otp-${user._id.toString()}`, 696, otp);
    otpStore.del(attemptsKey(user._id.toString()));

    try {
      await sendEmail({
        to: user.email,
        subject: "Forgot Password OTP",
        template: "forgotPasswordOTP",
        variables: {
          userName: user.name,
          otpCode: otp,
        },
      });
    } catch (err) {
      // Previously fire-and-forget: a send failure (bad SMTP creds, a
      // missing template in build/, a transient network error) was
      // silently swallowed and the API still claimed success below.
      console.error("forgotPassword: sendEmail failed:", err);
      return res.status(502).send({
        message: "Failed to send the password reset email. Please try again.",
      });
    }

    res.status(200).send({
      message: "Password Reset Email sent.",
    });
  }

  static async verifyOTP(req: Request, res: Response) {
    const { email, otp } = req.body;

    if (typeof email !== "string" || !isValidEmail(email)) {
      return res.status(400).send({
        message: "Email is required.",
      });
    }

    if (typeof otp !== "string" || otp === "") {
      return res.status(400).send({
        message: "OTP is required.",
      });
    }

    const user = await User.findOne({ email });

    if (!user || Object.keys(user).length == 0) {
      return res.status(400).send({
        message: "User not found.",
      });
    }

    const otpError = await checkOtp(user._id.toString(), otp);
    if (otpError) {
      return res.status(otpError.status).send({ message: otpError.message });
    }

    res.status(200).send({
      message: "OTP verified successfully.",
    });
  }

  static async resetPassword(req: Request, res: Response) {
    const { email, password, otp } = req.body;
    console.log(email, password, otp);
    const PASSWORD_REGEX =
      /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*(),.?":{}|<>]).{8,20}$/;
    const saltRounds = 10;

    if (typeof email !== "string" || !isValidEmail(email)) {
      return res.status(400).send({
        message: "Email is required.",
      });
    }

    // Validate the password against the regex
    if (!PASSWORD_REGEX.test(password)) {
      return res.status(400).send({
        message:
          "Password must be 8-20 characters long, with at least one uppercase, one lowercase, one digit, and one special character.",
      });
    }

    if (!password || password === "") {
      return res.status(400).send({
        message: "Password is required.",
      });
    }

    if (typeof otp !== "string" || otp === "") {
      return res.status(400).send({
        message: "token is required.",
      });
    }

    const user = await User.findOne({ email });

    if (!user || Object.keys(user).length == 0) {
      return res.status(400).send({
        message: "User not found.",
      });
    }

    const otpError = await checkOtp(user._id.toString(), otp);
    if (otpError) {
      return res.status(otpError.status).send({ message: otpError.message });
    }

    bcrypt.hash(password, saltRounds).then(async function (hash: string) {
      user.password = hash;
      await user.save();

      await otpStore.del(`otp-${user._id.toString()}`);
      await otpStore.del(attemptsKey(user._id.toString()));

      res.status(200).send({
        message: "Password reset successfully.",
      });
    });
  }
}

module.exports = ForgotPasswordController;
