import response from "../../response.js";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import nodemailer from "nodemailer";
import models from "../../models/index.js";

const transporter = nodemailer.createTransport({
  service: "gmail",
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS,
  },
});

export const registerAdmin = async (req, res) => {
  const { NAME_USERS, EMAIL, PASSWORD, CONFIRM_PASSWORD } = req.body;

  if (!NAME_USERS) {
    return response(400, null, "Name is required!", res);
  }

  if (!EMAIL) {
    return response(400, null, "Email is required!", res);
  }

  if (!PASSWORD) {
    return response(400, null, "Password is required!", res);
  }

  if (!CONFIRM_PASSWORD) {
    return response(400, null, "Confirm Password is required!", res);
  }

  if (PASSWORD !== CONFIRM_PASSWORD) {
    return response(400, null, "Passwords do not match!", res);
  }

  try {
    const salt = await bcrypt.genSalt(10);
    const hashedPassword = await bcrypt.hash(PASSWORD, salt);

    const newUser = await models.User.create({
      NAME_USERS: NAME_USERS,
      EMAIL: EMAIL,
      PASSWORD: hashedPassword,
      ROLE: "admin",
    });

    const adminResponse = {
      ID: newUser.ID,
      NAME_USERS: newUser.NAME_USERS,
      EMAIL: newUser.EMAIL,
      ROLE: newUser.ROLE,
    };

    response(200, adminResponse, "Admin registration successful", res);
  } catch (error) {
    console.log(error);

    if (error.name === "SequelizeUniqueConstraintError") {
      return response(400, null, "Email already registered!", res);
    }

    response(500, null, "Internal Server Error", res);
  }
};

export const registerTeacher = async (req, res) => {
  const { NAME_USERS, EMAIL, NIP, PASSWORD, CONFIRM_PASSWORD } = req.body;

  if (!NAME_USERS) {
    return response(400, null, "Name is required!", res);
  }

  if (!EMAIL) {
    return response(400, null, "Email is required!", res);
  }

  if (!NIP) {
    return response(400, null, "NIP is required for teachers!", res);
  }

  if (!PASSWORD) {
    return response(400, null, "Password is required!", res);
  }

  if (!CONFIRM_PASSWORD) {
    return response(400, null, "Confirm Password is required!", res);
  }

  if (PASSWORD !== CONFIRM_PASSWORD) {
    return response(400, null, "Passwords do not match!", res);
  }

  const transaction = await models.db.transaction();

  try {
    const salt = await bcrypt.genSalt(10);
    const hashedPassword = await bcrypt.hash(PASSWORD, salt);

    const newUser = await models.User.create(
      {
        NAME_USERS: NAME_USERS,
        EMAIL: EMAIL,
        PASSWORD: hashedPassword,
        ROLE: "teacher",
      },
      { transaction }
    );

    await models.Teacher.create(
      {
        ID: newUser.ID,
        NIP: NIP,
      },
      { transaction }
    );

    await transaction.commit();

    const teacherResponse = {
      ID: newUser.ID,
      NAME_USERS: newUser.NAME_USERS,
      EMAIL: newUser.EMAIL,
      NIP: NIP,
      ROLE: newUser.ROLE,
    };

    response(200, teacherResponse, "Teacher registration successful", res);
  } catch (error) {
    console.log(error);
    await transaction.rollback();

    if (error.name === "SequelizeUniqueConstraintError") {
      const field = error.original.sqlMessage.match(/for key '(.+)'/)[1];

      if (field === "teacher_unique_nip") {
        return response(400, null, "NIP already registered!", res);
      }

      if (field === "user_unique_email") {
        return response(400, null, "Email already registered!", res);
      }
    }

    response(500, null, "Internal Server Error", res);
  }
};

export const registerStudent = async (req, res) => {
  const { NAME_USERS, EMAIL, NISN, PASSWORD, CONFIRM_PASSWORD } = req.body;

  if (!NAME_USERS) {
    return response(400, null, "Name is required!", res);
  }

  if (!EMAIL) {
    return response(400, null, "Email is required!", res);
  }

  if (!NISN) {
    return response(400, null, "NISN is required for students!", res);
  }

  if (!PASSWORD) {
    return response(400, null, "Password is required!", res);
  }

  if (!CONFIRM_PASSWORD) {
    return response(400, null, "Confirm Password is required!", res);
  }

  if (PASSWORD !== CONFIRM_PASSWORD) {
    return response(400, null, "Passwords do not match!", res);
  }

  const transaction = await models.db.transaction();

  try {
    const salt = await bcrypt.genSalt(10);
    const hashedPassword = await bcrypt.hash(PASSWORD, salt);

    const newUser = await models.User.create(
      {
        NAME_USERS: NAME_USERS,
        EMAIL: EMAIL,
        PASSWORD: hashedPassword,
        ROLE: "student",
      },
      { transaction }
    );

    await models.Student.create(
      {
        ID: newUser.ID,
        NISN: NISN,
      },
      { transaction }
    );

    await transaction.commit();

    const studentResponse = {
      ID: newUser.ID,
      NAME_USERS: newUser.NAME_USERS,
      EMAIL: newUser.EMAIL,
      NISN: NISN,
      ROLE: newUser.ROLE,
    };

    response(200, studentResponse, "Student registration successful", res);
  } catch (error) {
    console.log(error);
    await transaction.rollback();

    if (error.name === "SequelizeUniqueConstraintError") {
      const field = error.original.sqlMessage.match(/for key '(.+)'/)[1];

      if (field === "student_unique_nisn") {
        return response(400, null, "NISN already registered!", res);
      }

      if (field === "user_unique_email") {
        return response(400, null, "Email already registered!", res);
      }
    }

    response(500, null, "Internal Server Error", res);
  }
};

export const loginUser = async (req, res) => {
  const { EMAIL, PASSWORD } = req.body;

  if (!EMAIL) {
    return response(400, null, "Email is required!", res);
  }

  if (!PASSWORD) {
    return response(400, null, "Password is required!", res);
  }

  try {
    const user = await models.User.findOne({ where: { EMAIL } });

    if (!user) {
      return response(404, null, "User data not found!", res);
    }

    const validPassword = await bcrypt.compare(PASSWORD, user.PASSWORD);
    if (!validPassword) {
      return response(401, null, "The password you entered is incorrect!", res);
    }

    const accessToken = jwt.sign(
      { ID: user.ID, ROLE: user.ROLE },
      process.env.ACCESS_TOKEN_SECRET,
      { expiresIn: "3h" }
    );

    const refreshToken = jwt.sign(
      { ID: user.ID, ROLE: user.ROLE },
      process.env.REFRESH_TOKEN_SECRET,
      { expiresIn: "7d" }
    );

    await models.User.update(
      { REFRESH_TOKEN: refreshToken },
      { where: { ID: user.ID } }
    );

    res.cookie("refreshToken", refreshToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      // sameSite: "Strict",
      maxAge: 7 * 24 * 60 * 60 * 1000
    });

    const userResponse = {
      ID: user.ID,
      NAME_USERS: user.NAME_USERS,
      EMAIL: user.EMAIL,
      ROLE: user.ROLE,
      TOKEN: `Bearer ${accessToken}`,
      REFRESH_TOKEN: refreshToken,
    };

    response(200, userResponse, "Login successful", res);
  } catch (error) {
    console.log(error);
    response(500, null, "Internal Server Error", res);
  }
};

export const refreshToken = async (req, res) => {
  const refreshToken = req.cookies?.refreshToken || req.body?.REFRESH_TOKEN;

  if (!refreshToken) {
    return response(400, null, "Refresh token is required!", res);
  }

  try {
    const user = await models.User.findOne({
      where: { REFRESH_TOKEN: refreshToken },
    });

    if (!user) {
      return response(403, null, "Invalid refresh token!", res);
    }

    let decoded;
    try {
      decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
    } catch (err) {
      if (err.name === "TokenExpiredError") {
        return response(
          401,
          null,
          "Refresh token expired. Please login again.",
          res
        );
      }
      return response(403, null, "Invalid refresh token!", res);
    }

    if (decoded.ID !== user.ID) {
      return response(403, null, "Invalid refresh token data!", res);
    }

    const newAccessToken = jwt.sign(
      { ID: user.ID, ROLE: user.ROLE },
      process.env.ACCESS_TOKEN_SECRET,
      { expiresIn: "3h" }
    );

    const newRefreshToken = jwt.sign(
      { ID: user.ID, ROLE: user.ROLE },
      process.env.REFRESH_TOKEN_SECRET,
      { expiresIn: "7d" }
    );

    await models.User.update(
      { REFRESH_TOKEN: newRefreshToken },
      { where: { ID: user.ID } }
    );

    res.cookie("refreshToken", newRefreshToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      // sameSite: "Strict",
      maxAge: 7 * 24 * 60 * 60 * 1000
    });

    response(
      200,
      { TOKEN: `Bearer ${newAccessToken}`, REFRESH_TOKEN: newRefreshToken },
      "Token refreshed successfully",
      res
    );
  } catch (error) {
    console.log(error);
    response(500, null, "Internal Server Error", res);
  }
};

export const logoutUser = (req, res) => {
  response(200, null, "You have successfully logged out.", res);
};

export const forgotPassword = async (req, res) => {
  const { EMAIL } = req.body;

  if (!EMAIL) {
    return response(400, null, "Email is required!", res);
  }

  try {
    const user = await models.User.findOne({ where: { EMAIL: EMAIL } });

    if (!user) {
      return response(404, null, "Email is not registered!", res);
    }

    const resetToken = jwt.sign(
      { id: user.ID },
      process.env.RESET_PASSWORD_SECRET,
      {
        expiresIn: "1h",
      }
    );

    const resetLink = `${process.env.CLIENT_URL}/resetPassword/${resetToken}`;

    const mailOptions = {
      from: process.env.EMAIL_USER,
      to: user.EMAIL,
      subject: "Password Reset",
      text: `You are receiving this because you (or someone else) have requested the reset of the password for your account.
      Please click on the following link, or paste this into your browser to complete the process:
      ${resetLink}
      If you did not request this, please ignore this email and your password will remain unchanged.`,
    };

    await transporter.sendMail(mailOptions);

    response(200, null, "Password reset email sent successfully!", res);
  } catch (error) {
    console.log(error);
    response(500, null, "Internal Server Error", res);
  }
};

export const resetPassword = async (req, res) => {
  const { TOKEN, NEW_PASSWORD, CONFIRM_NEW_PASSWORD } = req.body;

  if (!TOKEN) {
    return response(400, null, "Token is required!", res);
  }

  if (!NEW_PASSWORD) {
    return response(400, null, "New password is required!", res);
  }

  if (!CONFIRM_NEW_PASSWORD) {
    return response(400, null, "Confirm new password is required!", res);
  }

  if (NEW_PASSWORD !== CONFIRM_NEW_PASSWORD) {
    return response(400, null, "Passwords do not match!", res);
  }

  try {
    const decoded = jwt.verify(TOKEN, process.env.RESET_PASSWORD_SECRET);
    const user = await models.User.findOne({ where: { ID: decoded.id } });

    if (!user) {
      return response(404, null, "User data not found!", res);
    }

    const salt = await bcrypt.genSalt(10);
    const hashedPassword = await bcrypt.hash(NEW_PASSWORD, salt);

    user.PASSWORD = hashedPassword;
    await user.save();

    response(200, null, "Password has been reset successfully!", res);
  } catch (error) {
    console.log(error);
    if (error.name === "TokenExpiredError") {
      return response(400, null, "Reset token has expired!", res);
    } else {
      return response(500, null, "Internal Server Error", res);
    }
  }
};
