import fs from "fs";
import bcrypt from "bcrypt";
import CustomError from "../Utils/ResponseHandler/CustomError.js";
import { comparePassword } from "../Utils/SecuringPassword.js";
import { getFileContent, sendEmails } from "../Utils/SendEmail.js";
import {
  ChangePasswordValidator,
  completeProfileValidator,
  ForgetPasswordValidator,
  LoginValidator,
  RegisterValidator,
  ResetPasswordValidator,
  VerifyOtpValidator,
} from "../Utils/Validator/UserValidator.js";
import OTP from "../DB/Model/otp.model.js";
import User from "../DB/Model/user.model.js";
import asyncErrorHandler from "../Utils/asyncErrorHandler.js";
import constants from "../Utils/constants.js";
import moment from "moment";
import envVars from "../Config/env-vars.js";
import FileUpload from "../DB/Model/fileUpload.model.js";
import { jwtGen, expireToken } from "../Utils/AccessTokenManagement/Tokens.js";
import { getNearbyUsers, handleNearbyGroup } from "../Utils/nearByUser.js";
import Wallet from "../DB/Model/payment/wallet.js";
import ConnectAccount from "../DB/Model/payment/connect-account.model.js";
import { checkUserConnectAccount } from "../Utils/Stripe/index.js";
import Stripe from "stripe";

const stripe = new Stripe(envVars.stripeSecretKey);


/**
 * Authenticate User
 *
 * @public
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const login = asyncErrorHandler(async (req, res, next) => {
  const { error } = LoginValidator.validate(req.body);
  if (error) {
    return next({
      statusCode: constants.UNPROCESSABLE_ENTITY,
      message: error.details[0].message,
    });
  }

  const { fcmToken } = req.body;

  const { user, accessToken } = await User.ValidateUserAndGenerateToken(req.body);
  if (fcmToken) {
    user.fcmToken = fcmToken;
    await user.save();
  }

  const nearbyUsers = await getNearbyUsers(user._id, 10);

  let stripeResponse = {};
  try {
    let userConnect = await ConnectAccount.findOne({ user: user._id });
    let checkUserConnectAccountData = null;

    if (user.stripe_id) {
      checkUserConnectAccountData = await checkUserConnectAccount(user.stripe_id, user);
    } else {
      checkUserConnectAccountData = await checkUserConnectAccount(null, user);
      await User.findByIdAndUpdate(
        user._id,
        { stripe_id: checkUserConnectAccountData.account_id },
        { new: true }
      );
    }

    if (checkUserConnectAccountData.success) {
      if (!userConnect) {
        await ConnectAccount.create({
          user: user._id,
          connect_account_id: checkUserConnectAccountData.account_id,
        });
      }

      await User.findByIdAndUpdate(user._id, { stripe_connected: true }, { new: true });

      stripeResponse = {
        success: true,
        message: "Stripe account verified and connected successfully!",
      };
    } else {
      const accountLink = await stripe.accountLinks.create({
        account: checkUserConnectAccountData.account_id,
        refresh_url: "http://localhost:3090/api/v1/reauth",
        return_url: "http://localhost:3090/api/v1/return",
        type: "account_onboarding",
      });

      await User.findByIdAndUpdate(
        user._id,
        { stripe_id: checkUserConnectAccountData.account_id, stripe_connected: false },
        { new: true }
      );

      stripeResponse = {
        success: false,
        message:
          checkUserConnectAccountData.message ||
          "Stripe account is not fully completed yet. Please finish onboarding.",
        onboarding_url: accountLink.url,
      };
    }

    let wallet = await Wallet.findOne({ user: user._id }).populate("user", "name email");
    if (!wallet) {
      wallet = await Wallet.create({ user: user._id, amount: 0 });
    }

    if (!wallet.customer_id) {
      const customer = await stripe.customers.create({
        name: user.name || "Unnamed User",
        email: user.email || undefined,
      });

      wallet.customer_id = customer.id;
      await wallet.save();
    }
  } catch (e) {
    stripeResponse = { success: false, message: e.message };
  }

  res.status(constants.OK).json({
    data: {
      token: accessToken,
      user: user.transform(),
      nearbyGroupSuggestion: nearbyUsers,
      stripe: stripeResponse,
    },
    success: true,
  });
});

/**
 * Register a new User
 *
 * @public
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const signup = async (req, res, next) => {
  const { error } = RegisterValidator.validate(req.body);
  const file = req.file;

  if (error) {
    return next({
      statusCode: constants.UNPROCESSABLE_ENTITY,
      message: error.details[0].message,
    });
  }

  try {
    const user = new User({ ...req.body });
    const savedUser = await user.save();
    console.log(savedUser, "savedUser");


    let imageId = null;
    if (file) {
      const fileUploaded = await FileUpload.create({
        file: file.filename,
        fileType: file.mimetype,
        user: savedUser._id,
      });

      imageId = fileUploaded._id;
      savedUser.image = imageId;
      await savedUser.save();
    }

    const randomOtp = Math.floor(Math.random() * 9000) + 1000;
    await OTP.create({ otpKey: randomOtp, user: savedUser._id, reason: "register" });

    let template = await getFileContent("src/Static/create-user.html");
    template = template.replace("{{verification}}", randomOtp);
    template = template.replace("{{email}}", savedUser.email);

    sendEmails(savedUser.email, "OTP for Registration", template, null, (err) => {
      if (err) {
        return next(CustomError.badRequest(err.message));
      }
    });

    const tokenPayload = {
      user: { email: savedUser.email, _id: savedUser._id, userType: savedUser.userType },
      exp: moment().add(envVars.jwtExpirationInterval, "hours").unix(),
      iat: moment().unix(),
      sub: savedUser._id,
    };

    const accessToken = await jwtGen(tokenPayload);

    const populatedUser = await User.findById(savedUser._id).populate("image");

    const stripeCustomer = await stripe.customers.create({
      email: savedUser.email,
      name: savedUser.full_name,
    });

    await Wallet.create({ user: savedUser._id, customer_id: stripeCustomer.id });

    // ✅ Default stripe_connected: false
    await User.findByIdAndUpdate(savedUser._id, { stripe_connected: false }, { new: true });

    return res.status(constants.CREATED).json({
      data: { user: populatedUser.transform(), token: accessToken },
      success: true,
      message: "User Registered Successfully",
    });
  } catch (error) {
    return next(User.checkDuplication(error));
  }
};

/**
 * Verify the new User
 *
 * @public
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const verifyOtp = asyncErrorHandler(async (req, res, next) => {
  const { user } = req;
  const { error } = VerifyOtpValidator.validate(req.body);
  if (error)
    return next({
      statusCode: constants.UNPROCESSABLE_ENTITY,
      message: error.details[0].message,
    });

  const userOtp = await OTP.findOne({ _id: user.otp, reason: req.body.otpType });
  if (!userOtp) {
    return next(CustomError.badRequest(constants.NO_RECORD_FOUND));
  }

  const compareOtp = await bcrypt.compare(req.body.otp, userOtp.otpKey);
  if (!compareOtp) {
    return next(CustomError.badRequest(constants.INVALID_OTP));
  }

  expireToken(req.bearerToken);

  await OTP.updateOne(
    { _id: user.otp },
    { $set: { otpUsed: true } },
    { new: true }
  );

  const tokenPayload = {
    user: { email: user.email, _id: user._id, userType: user.userType },
    exp: moment().add(envVars.jwtExpirationInterval, "hours").unix(),
    iat: moment().unix(),
    sub: user._id,
  };

  const accessToken = await jwtGen(tokenPayload);

  let stripeResponse = {};
  try {
    let userConnect = await ConnectAccount.findOne({ user: user._id });
    let checkUserConnectAccountData = null;

    if (user.stripe_id) {
      checkUserConnectAccountData = await checkUserConnectAccount(
        user.stripe_id,
        user
      );
    } else {
      checkUserConnectAccountData = await checkUserConnectAccount(null, user);
      await User.findByIdAndUpdate(
        user._id,
        { stripe_id: checkUserConnectAccountData.account_id },
        { new: true }
      );
    }

    if (checkUserConnectAccountData.success) {
      if (!userConnect) {
        await ConnectAccount.create({
          user: user._id,
          connect_account_id: checkUserConnectAccountData.account_id,
        });
      }

      await User.findByIdAndUpdate(
        user._id,
        { stripe_connected: true },
        { new: true }
      );

      stripeResponse = {
        success: true,
        message: "Stripe account verified and connected successfully!",
      };
    } else {
      const accountLink = await stripe.accountLinks.create({
        account: checkUserConnectAccountData.account_id,
        refresh_url: "http://localhost:3090/api/v1/reauth",
        return_url: "http://localhost:3090/api/v1/return",
        type: "account_onboarding",
      });

      await User.findByIdAndUpdate(
        user._id,
        {
          stripe_id: checkUserConnectAccountData.account_id,
          stripe_connected: false,
        },
        { new: true }
      );

      stripeResponse = {
        success: false,
        message:
          checkUserConnectAccountData.message ||
          "Stripe account is not fully completed yet. Please finish onboarding.",
        onboarding_url: accountLink.url,
      };
    }

    let wallet = await Wallet.findOne({ user: user._id }).populate(
      "user",
      "name email"
    );
    if (!wallet) {
      wallet = await Wallet.create({ user: user._id, amount: 0 });
    }

    if (!wallet.customer_id) {
      const customer = await stripe.customers.create({
        name: user.name || "Unnamed User",
        email: user.email || undefined,
      });

      wallet.customer_id = customer.id;
      await wallet.save();
    }
  } catch (e) {
    stripeResponse = { success: false, message: e.message };
  }

  // ✅ Populate image field here
  const verifiedUser = await User.findOneAndUpdate(
    { email: user.email },
    { $set: { is_verified: true, access_token: accessToken } },
    { new: true }
  )
    .populate("image")
    .lean();

  const message =
    req.body.otpType == "register"
      ? "User Logged In Successfully"
      : "Now, User can Reset Password.";

  return res.status(constants.CREATED).json({
    data: {
      user: verifiedUser,
      token: accessToken,
      stripeResponse,
    },
    success: true,
    message,
  });
});

/**
 * Get Current User Profile
 *
 * @public
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const getMe = asyncErrorHandler(async (req, res) => {
  const { user } = req;
  const userModel = await User.findOne({ _id: user._id })
    .populate("image")
    .select("-password");
  res.status(constants.OK).json({
    data: userModel.transform(),
    success: true,
    message: "Fetch Current User Profile Successfully",
  });
});

/**
 * Generate OTP for forget password of user
 *
 * @public
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const forgetPassword = asyncErrorHandler(async (req, res, next) => {
  const { error } = ForgetPasswordValidator.validate(req.body);
  if (error)
    return next({
      statusCode: constants.UNPROCESSABLE_ENTITY,
      message: error.details[0].message,
    });

  const user = await User.findOne({
    $and: [{ email: req.body.email }, { is_deleted: false }],
  });
  if (!user) {
    return next(CustomError.notFound("No User exist with this email", 404));
  }

  // * Update all old OTPs
  await OTP.updateMany(
    { user: user._id, otpUsed: false },
    { $set: { otpUsed: true, expireAt: new Date() } }
  );

  // ✅ Yahan verification check add karte hain
  const otpReason = user.is_verified ? "forgetPassword" : "register";

  const otpPayload = {
    otpKey: Math.floor(Math.random() * 9000) + 1000,
    user: user._id,
    reason: otpReason,
  };
  console.log("🚀 ~ forgetPassword ~ otpPayload:", otpPayload);
  await OTP.create(otpPayload);

  const to = user.email;
  let template = await getFileContent("src/Static/ForgetPassword.html");
  template = template.replace("{{verification}}", otpPayload.otpKey);
  template = template.replace("{{email}}", to);

  sendEmails(to, "Forget Password OTP", template, null, (err) => {
    if (err) {
      return next(CustomError.badRequest(err.message));
    }
  });

  const tokenPayload = {
    user: { email: user.email, _id: user._id, userType: user.userType },
    exp: moment().add(envVars.jwtExpirationInterval, "hours").unix(),
    iat: moment().unix(),
    sub: user._id,
  };

  const accessToken = await jwtGen(tokenPayload);

  res.status(constants.OK).json({
    success: true,
    message: `OTP sent to email for ${otpReason}`,
    token: accessToken,
  });
});


/**
 * Match OTP to reset password of user
 *
 * @public
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const resetPassword = asyncErrorHandler(async (req, res, next) => {
  const { error } = ResetPasswordValidator.validate(req.body);
  if (error)
    return next({
      statusCode: constants.UNPROCESSABLE_ENTITY,
      message: error.details[0].message,
    });
  const { password } = req.body;
  const { user, bearerToken } = req;
  console.log("🚀 ~ resetPassword ~ user:", user);
  const userData = await User.findById(user._id);
  if (!user) {
    return next(CustomError.notFound("No User exist with this email", 404));
  }
  // TODO: Check whether the password is same to previous one
  // if(!comparePassword(userData.password, password)) {
  //   return
  // }
  // // ? Find User's generated OTP
  // const userOtp = await OTP.findById(user.otp);
  // // ? Compare User's generated OTP
  // const compareOtp = await bcrypt.compare(otp, userOtp.otpKey);
  // if (!compareOtp) {
  //   return next(CustomError.badRequest(constants.INVALID_OTP));
  // }
  // // * Update the generated OTP
  // await OTP.updateOne({ _id: userOtp._id }, { $set: { otpUsed: true } }, { new: true });

  // * Update all old OTPs
  await OTP.updateMany(
    { user: user._id, reason: "forgetPassword", otpUsed: false },
    { $set: { otpUsed: true, expireAt: new Date() } }
  );
  // ? Expire Previous Otp
  expireToken(bearerToken);
  // ? Generate New Token
  const tokenPayload = {
    user: {
      email: userData.email,
      _id: userData._id,
      userType: userData.userType,
    },
    exp: moment().add(envVars.jwtExpirationInterval, "hours").unix(),
    iat: moment().unix(),
    sub: userData._id,
  };
  // ? Generate Access Token
  const accessToken = await jwtGen(tokenPayload);

  userData.password = password;
  const savedUser = await userData.save();
  res.status(constants.OK).json({
    data: { user: savedUser.transform(), token: accessToken },
    success: true,
    message: "Reset Password Successfully",
  });
});

/**
 * change password of the current user
 *
 * @public
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const changePassword = asyncErrorHandler(async (req, res, next) => {
  const { error } = ChangePasswordValidator.validate(req.body);
  if (error) {
    return next(CustomError.badRequest(error.details[0].message));
  }
  const { user } = req;
  const { old_password, new_password } = req.body;

  const userData = await User.findOne({ _id: user._id });
  if (!userData) {
    return next(CustomError.badRequest(constants.NO_RECORD_FOUND));
  }
  const isMatch = comparePassword(old_password, userData.password);
  if (!isMatch) {
    return next(CustomError.badRequest("Old Password is Incorrect"));
  }
  userData.password = new_password;
  const updatedUser = await userData.save();
  res.status(constants.OK).json({
    data: updatedUser.transform(),
    success: true,
    message: "Password Changed Successfully",
  });
});

/**
 * Expire user current Access Token
 *
 * @public
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const logout = asyncErrorHandler(async (req, res) => {
  const { bearerToken } = req;

  expireToken(bearerToken);

  res
    .status(constants.OK)
    .json({ message: "User Logout Successfully", success: true });
});

/**
 * Soft Delete User Account
 *
 * @public
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */
const deleteAccount = asyncErrorHandler(async (req, res) => {
  const { user } = req;
  await User.updateOne({ _id: user._id }, { is_deleted: true }, { new: true });
  return res
    .status(200)
    .json({ message: "User Deleted Successfully", success: true });
});

const completeProfile = asyncErrorHandler(async (req, res, next) => {
  const { user, file } = req;
  const { error } = completeProfileValidator.validate(req.body);
  if (error)
    return next({
      statusCode: constants.UNPROCESSABLE_ENTITY,
      message: error.details[0].message,
    });
  if (user && !user.is_verified) {
    return next({
      statusCode: constants.UNAUTHORIZED,
      message: constants.NOT_VERIFIED,
    });
  }
  const data = Object.fromEntries(
    Object.entries(req.body).filter(
      ([_, v]) => v != null && v != "" && v != "" && v != "null"
    )
  );

  if (file) {
    fs.unlink("uploads/" + user?.image?.file, async (err) => {
      if (!err) {
        await FileUpload.deleteOne(user?.image?._id);
      }
    });
    const fileUploaded = await FileUpload.create({
      file: file.filename,
      fileType: file.mimetype,
      user: user._id,
    });
    data.image = fileUploaded._id;
  }
  data.is_profile_completed = true;
  if (data.dob) {
    data.dob = new Date(data.dob).toISOString();
  }
  const userData = await User.findByIdAndUpdate(user._id, data, { new: true })
    .populate("image")
    .select("-password");
  res.status(constants.OK).json({
    data: userData.transform(),
    success: true,
    message: "Profile Updated Successfully",
  });
});

const notificationToggle = async (req, res, next) => {
  try {
    const userId = req.user._id;

    // Current user ko fetch karo
    const user = await User.findById(userId).select("notification_on");
    if (!user) {
      return res.status(404).json({
        success: false,
        message: "User not found",
      });
    }

    // Toggle the notificationOn value
    const updatedUser = await User.findByIdAndUpdate(
      userId,
      { notification_on: !user.notification_on },
      { new: true, select: "notification_on  " }
    );

    return res.status(200).json({
      success: true,
      message: "Notification toggled successfully",
      data: {
        notification_on: updatedUser.notification_on,
      }
    });
  } catch (error) {
    return next(error);
  }
};

/**
 * join nearby group
 *
 * @public
 * @param {Request} req
 * @param {Response} res
 * @param {next} next
 */

const joinNearbyGroup = asyncErrorHandler(async (req, res, next) => {
  const userId = req.user._id;
  const { radiusInMiles = 10 } = req.body;

  const group = await handleNearbyGroup(userId, radiusInMiles);
  if (!group) {
    return next(CustomError.badRequest("No nearby users found to form a group."));
  }

  res.status(constants.OK).json({
    data: group,
    message: "Joined group successfully!",
    success: true,
  });
});


export {
  login,
  signup,
  verifyOtp,
  getMe,
  forgetPassword,
  resetPassword,
  changePassword,
  logout,
  deleteAccount,
  completeProfile,
  notificationToggle,
  joinNearbyGroup
};