import { CookieOptions, Request, Response } from "express";
import jwt from "jsonwebtoken";
import bcrypt from "bcrypt";
import * as helper from "../common/helper";
import * as authHelper from "../common/authHelper";
import * as env from "../config/env.config";
import { MaritalStatus, Status, UserRegisterPayload } from "../models/types";
import asyncHandler from "../common/asyncHandler";
import { createSession, getSessionDecision } from "../services/didit";
import * as mailHelper from "../common/mailHelper";
import {
  changePasswordTemplate,
  editProfileTemplate,
  forgotPasswordTemplate,
  registerTemplate,
} from "../common/templates";
import crypto from "crypto";
import { generateDbToken } from "../common/databaseHelper";
import User from "../models/user";
import Token from "../models/token";
import Account from "../models/account";
import Bank from "../models/bank";
import Level from "../models/level";
import LevelHistory from "../models/levelHistory";
import mongoose from "mongoose";
import { getScore } from "../services/userService";
import { downloadImage } from "../common/fileHelper";
import path, { parse } from "path";
import OperationPayment from "../models/operationPayment";
import { readFileSync } from "fs";
import Config from "../models/config";
import { buscar } from "../services/agileCheckService";
import FrequentQuestion from "../models/frequentQuestion";
import agileCheckLists from "../config/data/agileCheckLists";

/**
 * Test auth.
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const auth = async (req: Request, res: Response) => {
  const cookieName = authHelper.getAuthCookieName(req);
  const cookieOptions: CookieOptions = helper.clone(env.COOKIE_OPTIONS);

  let options = { expiresIn: env.JWT_EXPIRE_AT };
  cookieOptions.maxAge = env.JWT_EXPIRE_AT * 1000;

  const token = jwt.sign(
    { email: "edgar@gmail.com", roles: ["admin"] },
    env.JWT_SECRET,
    options
  );
  //res.cookie(cookieName, token, cookieOptions);

  return res
    .cookie(cookieName, token, cookieOptions)
    .status(200)
    .json({ message: "Login successful" });
};

/**
 * Login
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const login = asyncHandler(async (req: Request, res: Response) => {
  const { email, password, rememberMe } = req.body;

  if (!email || !password) {
    return res.status(400).json({
      success: false,
      message: "Correo y contraseña son requeridos.",
      code: "field_missing",
    });
  }

  const user = await User.findOne({
    email,
    status: { $nin: [Status.Deleted] },
  });

  if (!user) {
    return res.status(401).json({
      success: false,
      message: "El correo ingresado no esta registrado en Pasta.",
      code: "user_not_found",
    });
  }

  if (!user.isVerified) {
    return res.status(401).json({
      success: false,
      message: "El usuario no ha sido verificado.",
      code: "user_not_verified",
    });
  }

  if (!(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({
      success: false,
      message: "La contraseña ingresada es incorrecta.",
      code: "invalid_password",
    });
  }

  if (user.status === Status.Pending) {
    return res.status(401).json({
      success: false,
      message: "El estatus del usuario esta pendiente por aprobación",
      code: "user_pending",
    });
  }

  if (user.status === Status.Inactive) {
    return res.status(401).json({
      success: false,
      message: "El usuario está inactivo.",
      code: "user_inactive",
    });
  }

  let options: { expiresIn?: number };
  const cookieOptions: CookieOptions = helper.clone(env.COOKIE_OPTIONS);
  if (rememberMe) {
    options = {};
    cookieOptions.maxAge = 400 * 24 * 60 * 60 * 1000;
  } else {
    options = { expiresIn: env.JWT_EXPIRE_AT };
    cookieOptions.maxAge = env.JWT_EXPIRE_AT * 1000;
  }

  const token = jwt.sign(
    { id: user._id, email: user.email, roles: user.roles },
    env.JWT_SECRET,
    options
  );

  const nextLevel = await Level.findOne({ level: user.level + 1 });
  // const loggedUser: LoggedUser = {
  //   id: user._id,
  //   email: user.email,
  //   accessToken: token,
  // };

  const amountPending = await OperationPayment.aggregate([
    {
      $match: {
        user: user._id,
        status: { $nin: ["void", "paid"] },
      },
    },
    {
      $group: {
        _id: null, // Agrupa todos los documentos en un solo grupo para sumar todos los montos
        totalAmount: { $sum: "$amountUsd" }, // Calcula la suma de amountUsd
      },
    },
  ]).exec();

  const totalPendingAmount =
    amountPending.length > 0 ? amountPending[0].totalAmount : 0;

  return res.status(200).json({
    success: true,
    message: "Login successful",
    data: {
      id: user._id,
      name: user.name,
      lastname: user.lastname,
      email: user.email,
      level: user.level,
      points: parseFloat(user.points.toFixed(2)),
      maxAmount: user.maxAmount,
      availableAmount: user.maxAmount - totalPendingAmount,
      allowedFeeCount: user.allowedFeeCount,
      nextLevelPoints: nextLevel?.pointsRequired,
      accessToken: token,
    },
  });

  // const cookieName = authHelper.getAuthCookieName(req);
  // res.cookie(cookieName, token, env.COOKIE_OPTIONS);

  // return res.status(200).json({ message: "Login successful" });
});

/**
 * Get user
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const getUser = asyncHandler(async (req: Request, res: Response) => {
  const userId = new mongoose.Types.ObjectId(req.user!.id);
  const results = await User.aggregate([
    {
      $match: {
        _id: userId,
      },
    },
    {
      $lookup: {
        from: "Level", // Debe coincidir con el nombre de la colección en MongoDB
        let: { currentLevel: "$level" },
        pipeline: [
          {
            $match: {
              $expr: {
                $eq: ["$level", { $add: ["$$currentLevel", 1] }],
              },
            },
          },
          {
            $project: {
              _id: 0,
              pointsRequired: 1,
              name: 1,
            },
          },
        ],
        as: "nextLevelData",
      },
    },
    {
      $unwind: {
        path: "$nextLevelData",
        preserveNullAndEmptyArrays: true,
      },
    },
    {
      $project: {
        _id: 1,
        name: 1,
        lastname: 1,
        email: 1,
        level: 1,
        levelName: 1,
        points: 1,
        maxAmount: 1,
        allowedFeeCount: 1,
        nextLevelPoints: "$nextLevelData.pointsRequired",
        nextLevelName: "$nextLevelData.name",
      },
    },
  ]);

  const amountPending = await OperationPayment.aggregate([
    {
      $match: {
        user: userId,
        status: { $nin: ["void", "paid"] },
      },
    },
    {
      $group: {
        _id: null, // Agrupa todos los documentos en un solo grupo para sumar todos los montos
        totalAmount: { $sum: "$amountUsd" }, // Calcula la suma de amountUsd
      },
    },
  ]).exec();

  const totalPendingAmount =
    amountPending.length > 0 ? amountPending[0].totalAmount : 0;

  const resultData = {
    ...results[0],
    points: parseFloat(results[0].points.toFixed(2)),
    availableAmount: results[0].maxAmount - totalPendingAmount,
  };

  // if (!results || results.length === 0) {
  //   return res.status(401).json({ success: false, message: "Unauthorized", code: "unauthorized" });
  // }

  return res.status(200).json({
    success: true,
    message: "User retrieved successfully",
    data: resultData,
  });
});

/**
 * Get profile
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const getProfile = asyncHandler(async (req: Request, res: Response) => {
  const userId = new mongoose.Types.ObjectId(req.user!.id);

  const user = await User.findById(userId).select(
    "name lastname email document identificationType email phone birthDate address"
  );

  if (!user) {
    return res.status(404).json({
      success: false,
      message: "User not found",
      code: "user_not_found",
    });
  }

  // if(!user.address) user.address = null;

  return res.status(200).json({
    success: true,
    message: "User profile retrieved successfully",
    data: user,
  });
});

/**
 * Get user by id
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const getUserById = asyncHandler(async (req: Request, res: Response) => {
  const { userId } = req.params;
  console.log(userId);
  const results = await User.aggregate([
    {
      $match: {
        _id: new mongoose.Types.ObjectId(userId),
      },
    },
    {
      $lookup: {
        from: "levels", // Debe coincidir con el nombre de la colección en MongoDB
        let: { currentLevel: "$level" },
        pipeline: [
          {
            $match: {
              $expr: {
                $eq: ["$level", { $add: ["$$currentLevel", 1] }],
              },
            },
          },
          {
            $project: {
              _id: 0,
              pointsRequired: 1,
              name: 1,
            },
          },
        ],
        as: "nextLevelData",
      },
    },
    {
      $unwind: {
        path: "$nextLevelData",
        preserveNullAndEmptyArrays: true,
      },
    },
    {
      $project: {
        _id: 1,
        name: 1,
        lastname: 1,
        email: 1,
        level: 1,
        levelName: 1,
        points: 1,
        maxAmount: 1,
        allowedFeeCount: 1,
        document: 1,
        identificationType: 1,
        phone: 1,
        birthDate: 1,
        gender: 1,
        maritalStatus: 1,
        nextLevelPoints: "$nextLevelData.pointsRequired",
        nextLevelName: "$nextLevelData.name",
        selfEmployed: 1,
        enterprise: 1,
        dependents: 1,
        seniority: 1,
        income: 1,
        otherIncome: 1,
        education: 1,
        status: 1,
        verificationStatus: 1,
        notificationsConfig: 1,
        image: 1,
        documentImages: 1,
        agileCheckLists: 1,
        pep: 1,
        pepInfo: 1,
      },
    },
  ]);

  // if (!results || results.length === 0) {
  //   return res.status(401).json({ success: false, message: "Unauthorized", code: "unauthorized" });
  // }

  return res.status(200).json({
    success: true,
    message: "User retrieved successfully",
    data: results[0],
  });
});

/**
 * Get accounts for userId
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 *
 */
export const getAccounts = asyncHandler(async (req: Request, res: Response) => {
  const { userId } = req.params;

  const accounts = await Account.find({ user: userId }, "_id type number")
    .populate<{
      bank: env.Bank;
    }>({
      path: "bank",
      select: "code name",
    })
    .lean();

  return res.status(200).json({
    success: true,
    message: "Accounts retrieved successfully",
    data: accounts,
  });
});

/**
 * Get documents for user
 *
 * @export
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const getDocuments = asyncHandler(
  async (req: Request, res: Response) => {
    const { userId } = req.params;
    const results = await User.findById(userId);

    return res.status(200).json({
      success: true,
      message: "Documents retrieved successfully",
      data: results?.documentImages,
    });
  }
);

/**
 * Get all users
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const getAllUsers = asyncHandler(async (req: Request, res: Response) => {
  const results = await User.find({}).select(
    "_id name lastname email document status identificationType"
  );

  const resultData = results.map((user) => ({
    _id: user._id,
    name: user.name,
    lastname: user.lastname,
    email: user.email,
    document: user.document,
    status: user.status,
    statusName: helper.getUserStatusName(user.status),
    identificationType: user.identificationType,
  }));

  return res.status(200).json({
    success: true,
    message: "Users retrieved successfully",
    data: resultData,
  });
});

export const getList = asyncHandler(async (req: Request, res: Response) => {
  const results = await User.find(
    { status: Status.Active },
    "_id name lastname email identificationType document"
  );

  return res.status(200).json({
    success: true,
    message: "Users retrieved successfully",
    data: results,
  });
});

/**
 * Send token
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const sendRegisterToken = asyncHandler(
  async (req: Request, res: Response) => {
    const { email } = req.body;
    if (!email) {
      return res.status(400).json({ error: "email is required" });
    }

    const user = await User.findOne({ email, status: Status.Active });
    if (user) {
      return res.status(400).json({
        success: false,
        message: "El correo ingresado ya esta registrado en Pasta.",
        code: "user_exists",
      });
    }

    const token = await generateDbToken(email, "register");
    // Send email with the token
    const mailOptions = {
      from: env.SMTP_FROM,
      to: email,
      subject: "Completa tu registro en Pasta",
      html: registerTemplate(token.token),
    };

    mailHelper.sendMail(mailOptions);

    // // Send SMS with the token
    //await sendSms(user.phone, `Tu código de verificación es: ${token.token}`);

    // Send Whatsapp with the token
    // await sendWhatsapp(user.phone, `Tu código de verificación es: ${token.token}`);

    return res.status(200).json({ success: true, message: "Token enviado" });
  }
);

/**
 * Validate Token
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const validateToken = asyncHandler(
  async (req: Request, res: Response) => {
    const { token, email }: { token: string; email: string } = req.body;

    const tokenUser = await Token.findOne({ token, email });

    if (!tokenUser) {
      return res
        .status(400)
        .json({ success: false, message: "Token no válido" });
    }

    return res.status(200).json({ success: true, message: "Token válido" });
  }
);

/**
 * Validate document
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const validateDocument = asyncHandler(
  async (req: Request, res: Response) => {
    const { document } = req.body;
    const documentNumber = document?.replace(/[^0-9]/g, "");

    if (!documentNumber) {
      return res.status(400).json({
        success: false,
        message: "Falta el documento",
        code: "field_missing",
      });
    }

    const userExists = await User.exists({
      $expr: {
        $eq: [
          {
            $reduce: {
              input: {
                $regexFindAll: {
                  input: "$document",
                  regex: "[0-9]",
                },
              },
              initialValue: "",
              in: { $concat: ["$$value", "$$this.match"] },
            },
          },
          documentNumber,
        ],
      },
      status: { $ne: "deleted" },
    });

    if (userExists) {
      return res
        .status(400)
        .json({ success: false, message: "Documento ya esta registrado" });
    }

    return res
      .status(200)
      .json({ success: true, message: "Documento válido para registro" });
  }
);

/**
 * Register
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const register = asyncHandler(async (req: Request, res: Response) => {
  const { body }: { body: UserRegisterPayload } = req;

  if (!body.sessionId)
    return res.status(500).json({
      success: false,
      message: "sessionId required",
      code: "field_missing",
    });
  let diditSession;
  try {
    diditSession = await getSessionDecision(body.sessionId);
  } catch (error) {
    return res.status(500).json({
      success: false,
      message: "Se produjo un error verificando el usuario",
      code: "verification_error",
    });
  }

  body.status = "active";
  try {
    const listas = await Config.findOne({ key: "agile-check-lists" });

    if (listas && listas.value.length > 0) {
      const resp = await buscar(
        diditSession.id_verification?.first_name,
        diditSession.id_verification?.last_name,
        false,
        diditSession.id_verification?.document_number.slice(1),
        listas.value,
        "",
        0,
        0,
        "0"
      );

      if (resp.success) {
        if (
          resp.data.consultaRows.filter((c: any) => c.intFuente != 3).length > 1
        ) {
          return res.status(400).json({
            success: false,
            message: "Su solicitud de afiliacion ha sido rechazada.",
            code: "agile_check_result",
          });
        }

        if (
          resp.data.consultaRows.filter((c: any) => c.intFuente == 3).length > 0
        ) {
          body.status = "pending";
          body.agileCheckLists = resp.data.consultaRows.map(
            (c: any) => c.intFuente
          );
        }

        if (body.pep && body.pepInfo) {
          const pepFirstName = body.pepInfo.name.split(" ")[0];
          const pepLastName =
            body.pepInfo.name.split(" ").length > 1
              ? body.pepInfo.name.split(" ")[1]
              : "";
          const respPep = await buscar(
            pepFirstName,
            pepLastName,
            false,
            body.pepInfo.identification,
            listas.value,
            "",
            0,
            0,
            "0"
          );
          console.log(respPep);
          if (respPep.success) {
            if (respPep.data.consultaRows.length > 1) {
              body.status = "pending";
              body.pepInfo.agileCheckLists = respPep.data.consultaRows.map(
                (c: any) => c.intFuente
              );
            }
          }
        }
      }
    }
  } catch (error) {
    console.log(error);
  }

  //const score = getScore(body);

  body.isVerified = diditSession.status?.toLowerCase() === "approved";
  body.verificationStatus = diditSession.status;

  const level = await Level.findOne({ level: 1 });
  body.level = level!.level;
  body.levelName = level!.name;
  body.maxAmount = level!.creditLimit;
  body.allowedFeeCount = level!.allowedFeeCount;

  if (body.selfEmployed) delete body.enterprise;

  if (!body.account?.type || !body.account?.number || !body.account?.code)
    return res.status(500).json({
      success: false,
      message: "account required",
      code: "field_missing",
    });

  if (!body.token)
    return res.status(500).json({
      success: false,
      message: "token required",
      code: "field_missing",
    });
  const tokenUser = await Token.findOne({
    email: body.email,
    token: body.token,
    type: "register",
  });
  if (!tokenUser)
    return res.status(500).json({
      success: false,
      message: "Invalid token",
      code: "invalid_token",
    });

  if (body.password) {
    const salt = await bcrypt.genSalt(10);
    const { password } = body;
    const passwordHash = await bcrypt.hash(password, salt);
    body.password = passwordHash;
  }

  const user = await User.create(body);
  try {
    const bank = await Bank.findOne({ code: body.account.code });
    const account = new Account({
      user: user._id,
      bank: bank!._id,
      type: body.account.type,
      number: body.account.number,
    });
    await account.save();
  } catch (error) {
    console.log(error);
    const userId = user._id;
    await User.findByIdAndDelete(userId);
    return res.status(500).json({ success: false, error });
  }

  if (body.isVerified) {
    try {
      const filename = `${user._id}_${Date.now()}`;
      const fileNameImage = await downloadImage(
        diditSession.face_match.target_image,
        path.join(env.CDN_USERS, filename)
      );
      const fileNameDocument = await downloadImage(
        diditSession.id_verification.front_image,
        path.join(env.CDN_DOCUMENTS, `1_${filename}`)
      );

      user.image = fileNameImage!;
      user.documentImages = [
        {
          documentType: "1",
          image: fileNameDocument!,
          dateOfIssue: diditSession.id_verification?.date_of_issue,
          expirationDate: diditSession.id_verification?.expiration_date,
        },
      ];
      user.identificationType =
        diditSession.id_verification?.document_number?.[0];
      await user.save();
    } catch (error) {
      console.log(error);
    }
  }

  await Token.deleteMany({ email: body.email });
  const token = jwt.sign({ id: user._id, email: user.email }, env.JWT_SECRET, {
    expiresIn: env.JWT_EXPIRE_AT,
  });
  //throw Error("Not implemented");
  return res.status(200).json({
    success: true,
    message:
      user.status == Status.Active
        ? "Registro exitoso"
        : "Su solicitud de registro está pendiente por aprobación",
    data: {
      id: user._id,
      status: user.status,
      name: user.name,
      lastname: user.lastname,
      email: user.email,
      isVerified: user.isVerified,
      accessToken:
        user.isVerified && user.status == Status.Active ? token : null,
    },
  });
});

/**
 * Edit profile request
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const editProfile = asyncHandler(async (req: Request, res: Response) => {
  const user = await User.findById(req.user!.id);
  if (!user) {
    return res.status(401).json({
      success: false,
      message: "Unauthorized",
      code: "unauthorized",
    });
  }

  const token = await generateDbToken(user.email, "edit-profile", user._id);

  // Send email with the token
  const mailOptions = {
    from: env.SMTP_FROM,
    to: user.email,
    subject: "Modificación de perfil",
    html: editProfileTemplate(user.name.split(" ")[0], token.token),
  };

  mailHelper.sendMail(mailOptions);

  return res.status(200).json({
    success: true,
    message: "Verification email sent successfully",
  });
});

/**
 * Edit profile confirmation
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const editProfileConfirmation = asyncHandler(
  async (req: Request, res: Response) => {
    const { phone, email, address, token } = req.body;
    if (!phone || !email || !token) {
      return res.status(400).json({
        success: false,
        message: "Phone, Email and Token are required",
        code: "field_missing",
      });
    }

    const user = await User.findById(req.user!.id);
    if (!user) {
      return res.status(401).json({
        success: false,
        message: "Unauthorized",
        code: "unauthorized",
      });
    }

    const tokenUser = await Token.findOne({
      user: user._id,
      token,
      type: "edit-profile",
    });
    if (!tokenUser)
      return res.status(500).json({
        success: false,
        message: "Invalid token",
        code: "invalid_token",
      });

    user.phone = phone;
    user.email = email;
    user.address = address;

    await user.save();
    await Token.deleteMany({
      user: user._id,
      type: "edit-profile",
    });

    return res.status(200).json({
      success: true,
      message: "Profile updated successfully",
      data: { userId: user._id },
    });
  }
);

/**
 * Edit user
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const editUser = asyncHandler(async (req: Request, res: Response) => {
  const filteredBody = helper.filterObj(
    req.body,
    "name",
    "lastname",
    "identificationType",
    "document",
    "gender",
    "maritalStatus",
    "birthDate",
    "email",
    "phone",
    "education",
    "occupation",
    "dependents",
    "seniority",
    "income",
    "otherIncome",
    "selfEmployed",
    "enterprise"
  );
  try {
    console.log(filteredBody);
    // await User.findByIdAndUpdate(req.params.userId, filteredBody, {
    //   new: true,
    //   runValidators: true,
    // });

    const user = await User.findById(req.params.userId);
    if (!user) {
      return res.status(404).json({ message: "User not found" });
    }
    Object.assign(user, filteredBody);
    await user!.save();
  } catch (error: any) {
    if (error.code === 11000) {
      let message = "Error: Ya existe un registro con ";

      if (error.keyPattern && error.keyValue) {
        if (error.keyPattern.email) {
          message += `este correo electrónico (${error.keyValue.email}).`;
        } else if (error.keyPattern.document) {
          message += `este documento (${error.keyValue.document}).`;
        } else {
          message = "Error: Ya existe un registro con valores duplicados.";
        }
      } else {
        message = "Error: Ya existe un registro con valores duplicados.";
      }

      return res.status(400).json({
        success: false,
        code: "user_exists",
        message,
      });
    }
    return res.status(400).json({ success: false, message: error.message });
  }
  // if (!user) {
  //   return res.status(401).json({ success: false, message: "Unauthorized", code: "unauthorized" });
  // }

  // await user.save();

  return res
    .status(200)
    .json({ success: true, message: "User updated successfully" });
});

/**
 * Change password
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const changePassword = asyncHandler(
  async (req: Request, res: Response) => {
    const { oldPassword } = req.body;
    if (!oldPassword) {
      return res.status(400).json({
        success: false,
        message: "Old Password is required",
        code: "field_missing",
      });
    }

    const user = await User.findById(req.user!.id);
    if (!user) {
      return res.status(401).json({
        success: false,
        message: "Unauthorized",
        code: "unauthorized",
      });
    }

    if (!(await bcrypt.compare(oldPassword, user.password))) {
      return res.status(401).json({
        success: false,
        message: "Old Password is incorrect",
        code: "invalid_password",
      });
    }

    const token = await generateDbToken(user.email, "changePassword", user._id);

    // Send email with the token
    const mailOptions = {
      from: env.SMTP_FROM,
      to: user.email,
      subject: "Cambio de contraseña",
      html: changePasswordTemplate(user.name.split(" ")[0], token.token),
    };

    mailHelper.sendMail(mailOptions);

    return res.status(200).json({
      success: true,
      message: "Verification email sent successfully",
      data: { userId: user._id },
    });
  }
);

/**
 * Change password Confirmation
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const changePasswordConfirmation = asyncHandler(
  async (req: Request, res: Response) => {
    const { newPassword, token } = req.body;
    if (!newPassword || !token) {
      return res.status(400).json({
        success: false,
        message: "Password and token are required",
        code: "field_missing",
      });
    }

    const user = await User.findById(req.user!.id);
    if (!user) {
      return res.status(401).json({
        success: false,
        message: "Unauthorized",
        code: "unauthorized",
      });
    }

    const userToken = await Token.findOne({
      user: user._id,
      email: user.email,
      token,
      type: "change-password",
    });
    if (!userToken) {
      return res.status(401).json({
        success: false,
        message: "Invalid token",
        code: "invalid_token",
      });
    }

    const salt = await bcrypt.genSalt(10);
    const hash = await bcrypt.hash(newPassword, salt);

    user.password = hash;

    await user.save();

    await Token.deleteMany({
      user: user._id,
      email: user.email,
      type: "change-password",
    });

    return res.status(200).json({
      success: true,
      message: "Password updated successfully",
      data: { userId: user._id },
    });
  }
);

/**
 * Unregister user
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const unregister = asyncHandler(async (req: Request, res: Response) => {
  const userId = req.user!.id;
  const user = await User.findById(userId);

  const { motive } = req.body;

  if (!motive) {
    return res.status(400).json({
      success: false,
      message: "Motive is required",
      code: "field_missing",
    });
  }
  if (!user) {
    return res.status(404).json({
      success: false,
      message: "User not found",
      code: "user_not_found",
    });
  }
  user.status = "deleted";
  user.statusHistory.push({
    status: Status.Deleted,
    note: motive,
    createdAt: new Date(Date.now()),
  });
  await user.save();
  return res
    .status(200)
    .json({ success: true, message: "User unregistered successfully" });
});

/**
 * Refresh token
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const refreshToken = asyncHandler(
  async (req: Request, res: Response) => {
    const { refreshToken } = req.body;

    if (!refreshToken) {
      return res.status(400).json({ message: "Refresh token is required" });
    }

    // const user = await User.findOne({ refreshToken });

    // if (!user) {
    //   return res.status(401).json({ message: "Invalid refresh token" });
    // }
    const payload = jwt.verify(refreshToken, env.JWT_SECRET) as {
      id: string;
      email: string;
    };

    const token = jwt.sign(payload, env.JWT_SECRET, {
      expiresIn: env.JWT_EXPIRE_AT,
    });

    return res
      .status(200)
      .json({ message: "Refresh token successful", accessToken: token });
  }
);

/**
 * Forgot password
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const forgotPassword = asyncHandler(
  async (req: Request, res: Response) => {
    const { email } = req.body;

    if (!email) {
      return res.status(400).json({
        success: false,
        message: "Correo es requerido",
        code: "field_missing",
      });
    }

    const user = await User.findOne({ email, status: Status.Active });

    if (!user) {
      return res.status(401).json({
        success: false,
        message: "Correo no encontrado en sistema",
        code: "user_not_found",
      });
    }

    const token = await generateDbToken(email, "reset", user._id);

    // Send email with the token
    const mailOptions = {
      from: env.SMTP_FROM,
      to: user.email,
      subject: "Reseteo de contraseña",
      html: forgotPasswordTemplate(user.name, token.token),
    };

    await mailHelper.sendMail(mailOptions);

    // // Send SMS with the token
    //await sendSms(user.phone, `Tu código de verificación es: ${token.token}`);

    // Send Whatsapp with the token
    //  await sendWhatsapp(user.phone, `Tu código de verificación es: ${token.token}`);

    return res
      .status(200)
      .json({ success: true, message: "success", data: { userId: user._id } });
  }
);

/**
 * Reset password
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const resetPassword = asyncHandler(
  async (req: Request, res: Response) => {
    const { userId, token, password } = req.body;

    if (!token) {
      return res.status(400).json({
        success: false,
        message: "Token is required",
        code: "field_missing",
      });
    }

    if (!password) {
      return res.status(400).json({
        success: false,
        message: "Password is required",
        code: "field_missing",
      });
    }

    const userToken = await Token.findOne({ user: userId, token });

    if (!userToken) {
      return res.status(401).json({
        success: false,
        message: "Invalid token",
        code: "invalid_token",
      });
    }

    await Token.deleteMany({ user: userId });

    const user = (await User.findById(userId))!;

    const salt = await bcrypt.genSalt(10);
    const hash = await bcrypt.hash(password, salt);

    user.password = hash;

    await user.save();

    return res
      .status(200)
      .json({ success: true, message: "Reset password successful" });
  }
);

function encodeData(data: any) {
  const formattedData = JSON.stringify(data);
  return Buffer.from(formattedData, "utf-8");
}

/**
 * Webhook didit.
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const webhook = asyncHandler(async (req: Request, res: Response) => {
  // bodyParser.json({
  //   verify: (req, res, buf, encoding) => {
  //     if (buf && buf.length) {
  //       // Store the raw body in the request object
  //       req.rawBody = buf.toString(encoding || "utf8");
  //     }
  //   },
  // })
  try {
    const signature = req.get("X-Signature");
    const timestamp = req.get("X-Timestamp");
    // Ensure all required data is present
    if (!signature || !req.body || !env.DIDIT_WEBHOOK_SECRET_KEY) {
      return res.status(401).json({ message: "Unauthorized" });
    }

    // // 3) Validate the timestamp to ensure the request is fresh (within 5 minutes)
    // const currentTime = Math.floor(Date.now() / 1000);
    // const incomingTime = parseInt(timestamp, 10);
    // if (Math.abs(currentTime - incomingTime) > 300) {
    //   return res.status(401).json({ message: "Request timestamp is stale." });
    // }

    // 4) Generate an HMAC from the raw body using your shared secret
    const hmac = crypto.createHmac("sha256", env.DIDIT_WEBHOOK_SECRET_KEY);
    const expectedSignature = hmac.update(encodeData(req.body)).digest("hex");

    // 5) Compare using timingSafeEqual for security
    const expectedSignatureBuffer = Buffer.from(expectedSignature, "utf8");
    const providedSignatureBuffer = Buffer.from(signature, "utf8");

    if (
      expectedSignatureBuffer.length !== providedSignatureBuffer.length ||
      !crypto.timingSafeEqual(expectedSignatureBuffer, providedSignatureBuffer)
    ) {
      return res.status(401).json({
        message: `Invalid signature. Computed (${expectedSignature}), Provided (${signature})`,
      });
    }

    // 6) Parse the JSON and proceed (signature is valid at this point)
    const jsonBody = req.body;
    const { session_id, status, vendor_data } = jsonBody;

    const user = await User.findOne({ sessionId: session_id });
    if (!user)
      return res.status(401).json({ success: false, message: "Unauthorized" });

    user.isVerified = status.toLowerCase() === "approved";
    user.verificationStatus = status;

    if (user.isVerified) {
      try {
        const filename = `${user._id}_${Date.now()}`;
        const fileNameImage = await downloadImage(
          jsonBody.decision.face_match.target_image,
          path.join(env.CDN_USERS, filename)
        );
        const fileNameDocument = await downloadImage(
          jsonBody.decision.id_verification.front_image,
          path.join(env.CDN_DOCUMENTS, `1_${filename}`)
        );

        user.image = fileNameImage!;
        user.documentImages = [
          {
            documentType: "1",
            image: fileNameDocument!,
            dateOfIssue: jsonBody.decision.id_verification?.date_of_issue,
            expirationDate: jsonBody.decision.id_verification?.expiration_date,
          },
        ];
        user.identificationType =
          jsonBody.decision.id_verification?.document_number?.[0];
        await user.save();
      } catch (error) {
        console.log(error);
      }
    }

    await user.save();

    return res.json({ message: "Webhook event dispatched" });
  } catch (error) {
    console.error("Error in /webhook handler:", error);
    return res.status(401).json({ message: "Unauthorized" });
  }
});

export const updateProfile = asyncHandler(
  async (req: Request, res: Response) => {
    const { userId } = req.body;
    const user = await User.findById(userId);
    if (!user) {
      return res.status(404).json({ message: "User not found" });
    }

    const diditSession = await getSessionDecision(user.sessionId);

    const filename = `${user._id}_${Date.now()}`;
    const fileNameImage = await downloadImage(
      diditSession.face_match_match.target_image,
      path.join(env.CDN_USERS, filename)
    );
    const fileNameDocument = await downloadImage(
      diditSession.id_verification.front_image,
      path.join(env.CDN_DOCUMENTS, `1_${filename}`)
    );

    user.image = fileNameImage!;
    user.documentImages = [
      {
        documentType: "1",
        image: fileNameDocument!,
        dateOfIssue: diditSession.id_verification?.date_of_issue,
        expirationDate: diditSession.id_verification?.expiration_date,
      },
    ];
    user.identificationType =
      diditSession.id_verification?.document_number?.[0];
    // user.documentImages[0].dateOfIssue = diditSession.id_verification?.date_of_issue;
    // user.documentImages[0].expirationDate = diditSession.id_verification?.expiration_date;
    await user.save();

    return res.json(diditSession);
  }
);

export const updateProfiles = asyncHandler(
  async (req: Request, res: Response) => {
    const users = await User.find({});
    let count = 0;

    users.forEach(async (user) => {
      const diditSession = await getSessionDecision(user.sessionId);
      if (diditSession) {
        if (!user.image || !user.documentImages) {
          const filename = `${user._id}_${Date.now()}`;

          if (diditSession.face_match?.target_image) {
            const fileNameImage = await downloadImage(
              diditSession.face_match.target_image,
              path.join(env.CDN_USERS, filename)
            );
            user.image = fileNameImage!;
          }

          if (diditSession.id_verification?.front_image) {
            const fileNameDocument = await downloadImage(
              diditSession.id_verification.front_image,
              path.join(env.CDN_DOCUMENTS, `1_${filename}`)
            );
            user.documentImages = [
              {
                documentType: "1",
                image: fileNameDocument!,
                dateOfIssue: diditSession.id_verification?.date_of_issue,
                expirationDate: diditSession.id_verification?.expiration_date,
              },
            ];
          }

          // const fileNameImage = await downloadImage(diditSession.face_match.target_image, path.join(env.CDN_USERS, filename));
          // const fileNameDocument = await downloadImage(diditSession.id_verification.front_image, path.join(env.CDN_DOCUMENTS, `1_${filename}`));

          // user.image = fileNameImage!;
          // user.documentImages = [
          //   {
          //     documentType: "1",
          //     image: fileNameDocument!,
          //     dateOfIssue: diditSession.id_verification?.date_of_issue,
          //     expirationDate: diditSession.id_verification?.expiration_date,
          //   },
          // ];
          user.identificationType =
            diditSession.id_verification?.document_number?.[0];

          await user.save();
          count++;
        } else {
          if (user.image) {
            let filename = user.image;
            if (filename.includes("\\"))
              filename = filename.split("\\").pop() as string;
            else if (filename.includes("/"))
              filename = filename.split("/").pop() as string;
            user.image = filename;
          }
          if (user.documentImages) {
            user.documentImages.forEach((doc) => {
              let filename = doc.image;
              if (filename.includes("\\"))
                filename = filename.split("\\").pop() as string;
              else if (filename.includes("/"))
                filename = filename.split("/").pop() as string;
              doc.image = filename;
            });
          }
          await user.save();
          count++;
        }
      }
    });
    return res.json({ message: "Profiles updated successfully", count });
  }
);

export const downloadDocument = asyncHandler(
  async (req: Request, res: Response) => {
    const { userId, image } = req.params;
    const user = await User.findById(userId);
    if (!user) {
      return res.status(404).json({
        success: false,
        message: "User not found",
        code: "user_not_found",
      });
    }
    const document = user.documentImages.find((doc) => doc.image === image);
    if (!document) {
      return res.status(404).json({
        success: false,
        message: "Document not found",
        code: "document_not_found",
      });
    }
    res.download(path.join(env.CDN_DOCUMENTS, image));
  }
);

export const subscribePush = asyncHandler(
  async (req: Request, res: Response) => {
    const userId = req.user!.id;
    const { token } = req.body;
    const user = await User.findById(userId);
    if (!user) {
      return res.status(404).json({
        success: false,
        message: "User not found",
        code: "user_not_found",
      });
    }
    user.pushToken = token;
    await user.save();
    return res.json({
      success: true,
      message: "Push token generated successfully",
    });
  }
);

export const deleteUser = asyncHandler(async (req: Request, res: Response) => {
  const userId = req.params.userId;
  const user = await User.findById(userId);
  if (!user) {
    return res.status(404).json({
      success: false,
      message: "User not found",
      code: "user_not_found",
    });
  }
  user.status = "deleted";
  user.statusHistory.push({
    status: Status.Deleted,
    note: "Deleted by admin",
    createdAt: new Date(Date.now()),
  });
  await user.save();
  return res
    .status(200)
    .json({ success: true, message: "User deleted successfully" });
});

export const verifySession = asyncHandler(
  async (req: Request, res: Response) => {
    const { sessionId } = req.body;
    let diditSession;
    try {
      diditSession = await getSessionDecision(sessionId);
    } catch (error) {
      return res.status(500).json({
        success: false,
        message: "Se produjo un error verificando el usuario",
        code: "verification_error",
      });
    }

    if (diditSession.status?.toLowerCase() !== "approved")
      return res.status(400).json({
        success: false,
        message: "El usuario no ha sido verificado",
        code: "user_not_verified",
        data: diditSession,
      });

    const document = diditSession.id_verification?.document_number.slice(1);

    const user = await User.findOne({ document });
    if (user) {
      return res.status(400).json({
        success: false,
        message: "Documento ya esta registrado",
        code: "user_exists",
      });
    }

    const resultData = {
      document,
      identificationType: diditSession.id_verification?.document_number?.[0],
      name: diditSession.id_verification?.first_name,
      lastname: diditSession.id_verification?.last_name,
      birthDate: diditSession.id_verification?.date_of_birth,
      gender: diditSession.id_verification?.gender,
      maritalStatus:
        diditSession.id_verification?.marital_status?.toLowerCase(),
    };

    return res.status(200).json({
      success: true,
      message: "Sesión verificada correctamente",
      data: resultData,
    });
  }
);

export const getTermsAndConditions = asyncHandler(
  async (req: Request, res: Response) => {
    const filePath = path.join(
      env.TERMS_AND_CONDITIONS_PATH,
      "terminos-condiciones.html"
    );

    return res.status(200).json({
      success: true,
      message: "Terminos y condiciones descargados correctamente",
      data: readFileSync(filePath, "utf8"),
    });
  }
);

export const changeStatus = asyncHandler(
  async (req: Request, res: Response) => {
    const { userId } = req.params;
    const status = req.body.status;
    const user = await User.findById(userId);
    if (!user) {
      return res.status(404).json({
        success: false,
        message: "User not found",
        code: "user_not_found",
      });
    }
    user.status = status;
    user.statusHistory.push({
      status: status,
      note: `Cambio de estado a ${status} por ${req.user?.email}`,
      createdAt: new Date(Date.now()),
    });
    await user.save();
    return res
      .status(200)
      .json({ success: true, message: "User changed successfully" });
  }
);

export const createDiditSession = asyncHandler(
  async (req: Request, res: Response) => {
    const { body } = req;

    const session = await createSession(
      body.vendorData,
      body.metadata,
      body.callback
    );
    // if (!session.success) {
    //   return res.status(400).json({
    //     success: false,
    //     message: "Error al crear la sesión",
    //     data: session,
    //   });
    // }

    return res.status(200).json({
      success: true,
      message: "Sesión creada correctamente",
      data: session,
    });
  }
);

/**
 * Create user
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const create = asyncHandler(async (req: Request, res: Response) => {
  const { body }: { body: UserRegisterPayload } = req;

  body.isVerified = false;
  body.verificationStatus = "Not started";

  const level = await Level.findOne({ level: 1 });
  body.level = level!.level;
  body.levelName = level!.name;
  body.maxAmount = level!.creditLimit;
  body.allowedFeeCount = level!.allowedFeeCount;

  if (body.selfEmployed) delete body.enterprise;

  if (!body.account?.type || !body.account?.number || !body.account?.code)
    return res.status(500).json({
      success: false,
      message: "account required",
      code: "field_missing",
    });

  if (!body.token)
    return res.status(500).json({
      success: false,
      message: "token required",
      code: "field_missing",
    });
  // const tokenUser = await Token.findOne({
  //   email: body.email,
  //   token: body.token,
  //   type: "register",
  // });
  // if (!tokenUser)
  //   return res.status(500).json({
  //     success: false,
  //     message: "Invalid token",
  //     code: "invalid_token",
  //   });

  if (body.password) {
    const salt = await bcrypt.genSalt(10);
    const { password } = body;
    const passwordHash = await bcrypt.hash(password, salt);
    body.password = passwordHash;
  }

  const session = await createSession(null, null, null);

  body.sessionId = session.session_id;

  const user = await User.create(body);
  try {
    const bank = await Bank.findOne({ code: body.account.code });
    const account = new Account({
      user: user._id,
      bank: bank!._id,
      type: body.account.type,
      number: body.account.number,
    });
    await account.save();
  } catch (error) {
    const userId = user._id;
    await User.findByIdAndDelete(userId);
    return res.status(500).json({ success: false, error });
  }

  //throw Error("Not implemented");
  return res.status(200).json({
    success: true,
    message: "Register successful",
    data: {
      id: user._id,
      name: user.name,
      lastname: user.lastname,
      email: user.email,
      isVerified: user.isVerified,
    },
  });
});

export const getFrequentQuestions = asyncHandler(
  async (req: Request, res: Response) => {
    const results = await FrequentQuestion.find()
      .sort({ order: 1 })
      .select("question answer -_id");
    return res.status(200).json({
      success: true,
      message: "Frequent questions retrieved successfully",
      data: results,
    });
  }
);
