/* eslint-disable no-restricted-syntax */
import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import mongoose, { ObjectId } from 'mongoose';
import CacheClient from '../utils/cacheClient';
import { Company, User } from '../models';
import {
  signupSchema,
  verifyMailSchema,
  verificationLinkSchema,
  signinSchema,
  forgotPasswordSchema,
  resetPasswordSchema,
  changePasswordSchema,
} from '../validators/authValidator';
import { ICompanyInfo, Info, ISignUpUserInfo } from '../utils/types';
import { generatePasswordResetCode, generateToken, verifyToken } from '../utils/auth';
import { sendPasswordResetCode, sendPasswordResetMail, sendVerificationMail } from '../utils/email';
import { getDate } from '../utils/date';

export const logOut = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  try {
    const { user, headers } = req;
    const authorization = headers.authorization!;
    const token = authorization.slice(7, authorization.length);
    if (!user) {
      return res.status(400).json({
        success: false,
        message: 'User not signed in',
      });
    }
    const { exp, iat } = user;
    const time = exp - iat;
    await CacheClient.set(token, time);
    return res.status(200).json({
      success: true,
      message: 'Log out successful',
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
};

export const signUp = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  const { error, value } = signupSchema.body.validate(req.body);
  if (error) {
    return res.status(400).json({
      success: false,
      message: error.details[0].message,
    });
  }
  const {
    companyName,
    rcNumber,
    email,
    address,
    password,
    confirmPassword,
    phoneNumber,
  } = value;

  if (password !== confirmPassword)
    return res.status(401).json({
      success: false,
      message: 'Passwords do not match',
    });
  try {
    const exist = await Company.findOne({ $or: [{ rcNumber }, { email }, { phoneNumber }] });
    if (exist)
      return res.status(400).json({
        success: false,
        message: 'Company data already exists',
      });

    const companyData: ICompanyInfo = {
      companyName,
      rcNumber,
      email,
      address,
      phoneNumber,
    };
    const hashedPass = await bcrypt.hash(password, 8);

    // we use sessions to ensure if it fails it won't write to the db
    const session: mongoose.ClientSession = await mongoose.startSession();

    session.startTransaction();
    try {
      const companyInfo = new Company(companyData);
      const company = await companyInfo.save({ session });

      const userData: ISignUpUserInfo = {
        company_id: company._id,
        company_name: companyName,
        firstName: companyName,
        lastName: companyName,
        isCompany: true,
        isActive: false,
        userType: 'company',
        email,
        password: hashedPass,
        phoneNumber,
      };
      const userInfo = new User(userData);
      const user = await userInfo.save({ session });

      const payload: Info = {
        _id: user._id,
        company_id: user.company_id,
        firstName: user.firstName,
        lastName: user.lastName,
        role_id: user?.role_id,
        isCompany: user.isCompany,
        isActive: user.isActive,
        userType: user.userType,
        email: user.email,
        phoneNumber: user.phoneNumber,
      };
      try {
        await sendVerificationMail(payload);
        // Commit the changes
      } catch (err) {
        // Rollback any changes made in the database
        await session.abortTransaction();
        // Return the error
        session.endSession();
        return res.status(500).json({
          success: false,
          message:
            'Sending Email verification link failed. Re-request Email verification link',
        });
      }
      await session.commitTransaction();
    } catch (err: any) {
      // Rollback any changes made in the database
      await session.abortTransaction();

      // Return the error
      return res.status(401).json({
        success: false,
        message: err.message,
      });
    } finally {
      // Ending the session
      session.endSession();
    }

    return res.status(201).json({
      success: true,
      message:
        'Signup successful. Please check your email for verification',
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
};

export const verifyEmail = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  const { error, value } = verifyMailSchema.query.validate(req.query);
  if (error) {
    return res.status(400).json({
      success: false,
      message: error.details[0].message,
    });
  }
  const { token } = value;
  try {
    const { _id } = (await verifyToken(token)) as { _id: ObjectId };
    const existingUser = await User.findById(_id);
    if (!existingUser)
      return res.status(400).json({
        success: false,
        message: 'Invalid request please kindly register',
      });

    existingUser.isActive = true;
    await existingUser.save();

    return res.status(200).json({
      success: true,
      message: 'Email verified successfully. Please login to proceed',
    });
  } catch (err: any) {
    return res.status(401).json({
      success: false,
      message: err.message,
    });
  }
};

export const verificationLink = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  const { error, value } = verificationLinkSchema.body.validate(req.body);
  if (error) {
    return res.status(400).json({
      success: false,
      message: error!.details[0].message,
    });
  }

  const { email } = value;
  const existingUser = await User.findOne({ email });
  if (!existingUser)
    return res.status(404).json({
      success: false,
      message: 'User account does not exist',
    });

  if (!existingUser.isCompany)
    return res.status(403).json({
      success: false,
      message: 'Kindly contact organization admin to activate account',
    });

  const user = {
    _id: existingUser._id,
    company_id: existingUser.company_id,
    firstName: existingUser.firstName,
    lastName: existingUser.lastName,
    isCompany: existingUser.isCompany,
    isActive: existingUser.isActive,
    userType: existingUser.userType,
    email: existingUser.email,
    phoneNumber: existingUser.phoneNumber,
  };
  try {
    await sendVerificationMail(user);
    return res.status(200).json({
      success: true,
      message:
        'Link successfully sent. please check your email for email verification',
    });
  } catch (err) {
    return res.status(500).json({
      success: false,
      message: 'Sending Email verification link failed',
      err,
    });
  }
};

export const signIn = async (
  req: Request,
  res: Response,
): Promise<Response> => {
const { error, value } = signinSchema.body.validate(req.body);
  if (error) {
      return res.status(400).json({
      success: false,
      message: error.details[0].message,
    });
  }
  const { email, password } = value;
  try {
    const existingUser = await User.findOne({ email: {$regex: new RegExp(email, "i")} }).populate('role_id', {
      permissions: 1,
    });
    if (!existingUser)
      return res.status(400).json({
        success: false,
        message: 'Invalid email address',
      });

    const compare = await bcrypt.compare(password, existingUser.password);
    if (!compare)
      return res.status(403).json({
        success: false,
        message: 'Invalid password',
      });

    if (!existingUser.isActive)
      return res.status(403).json({
        success: false,
        message: 'Account is not active',
      });
    
    return res.status(200).json({
      success: true,
      message: 'Sign in successful',
      data: {
        token: generateToken(existingUser.toObject()),
        user: existingUser,
      },
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
};

export const getProfile = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  const { _id } = req.user!;
  const existingUser = await User.findById(_id);
  if (!existingUser)
    return res.status(404).json({
      success: false,
      message: 'User account does not exist',
    });

  try {
    return res.status(200).json({
      success: true,
      message: 'Get profile successful',
      data: {
        user: existingUser,
      },
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
};

export const mobileForgotPassword = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  const { error, value } = forgotPasswordSchema.body.validate(req.body);
  if (error) {
    return res.status(400).json({
      success: false,
      message: error.details[0].message,
    });
  }
  const { email } = value;
  const existingUser = await User.findOne({ email });
  if (!existingUser)
    return res.status(400).json({
      success: false,
      message: 'Invalid email address',
    });

  const user = {
    _id: existingUser._id,
    company_id: existingUser.company_id,
    firstName: existingUser.firstName,
    lastName: existingUser.lastName,
    isCompany: existingUser.isCompany,
    isActive: existingUser.isActive,
    userType: existingUser.userType,
    role_id: existingUser?.role_id,
    email: existingUser.email,
    phoneNumber: existingUser.phoneNumber,
  };
  try {
    const resetCode = generatePasswordResetCode();
    const date = getDate(); //get and return dates as current and expiry

    await sendPasswordResetCode(user, resetCode);
    return res.status(200).json({
      success: true,
      message: 'Password reset code has been sent to your email successfully',
      code: resetCode,
      created: date[0],
      expiry: date[1],
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: 'Sending password reset code failed',
    });
  }
};

export const forgotPassword = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  const { error, value } = forgotPasswordSchema.body.validate(req.body);
  if (error) {
    return res.status(400).json({
      success: false,
      message: error.details[0].message,
    });
  }
  const { email } = value;
  const existingUser = await User.findOne({ email });
  if (!existingUser)
    return res.status(400).json({
      success: false,
      message: 'Invalid email address',
    });

  const user = {
    _id: existingUser._id,
    company_id: existingUser.company_id,
    firstName: existingUser.firstName,
    lastName: existingUser.lastName,
    isCompany: existingUser.isCompany,
    isActive: existingUser.isActive,
    userType: existingUser.userType,
    role_id: existingUser?.role_id,
    email: existingUser.email,
    phoneNumber: existingUser.phoneNumber,
  };
  try {
    await sendPasswordResetMail(user);
    return res.status(200).json({
      success: true,
      message: 'Password reset link has been sent to your email successfully',
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: 'Sending password reset link failed',
    });
  }
};

export const resetPassword = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  const { error: errorBody, value: valueBody } =
    resetPasswordSchema.body.validate(req.body);

  const { error: errorQuery, value: valueQuery } =
    resetPasswordSchema.query.validate(req.query);

  if (errorBody || errorQuery) {
    const error = errorBody || errorQuery;
    return res.status(400).json({
      success: false,
      message: error?.details[0].message,
    });
  }
  const { token } = valueQuery;
  const { newPassword, confirmPassword } = valueBody;

  if (newPassword !== confirmPassword)
    return res.status(400).json({
      success: false,
      message: 'Password does not a match',
    });

  try {
    let _id;
    //comfirm if the token is an email or not
    const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    const isEmail = (dToken: string) => emailRegex.test(dToken);
    if (isEmail(token)){
      //make sure the user exist and the user is a rider if token is an email
      const emailUser = await User.findOne({ email: token, userType: 'rider' });
      if (!emailUser)
        return res.status(404).json({
          success: false,
          message: 'It\'s either we can not find any user with email ('+token+'), or the user is not a rider. Please try again.',
        });
      _id = emailUser._id;
    } else {
      _id = (await verifyToken(token)) as { _id: ObjectId };
    }

    const existingUser = await User.findById(_id);
    if (!existingUser)
      return res.status(400).json({
        success: false,
        message: 'Invalid request',
      });

    existingUser.password = await bcrypt.hash(newPassword, 8);
    await existingUser.save();
    return res.status(201).json({
      success: true,
      message: 'Password reset successful',
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
};

export const changePassword = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  const { error, value } = changePasswordSchema.body.validate(req.body);
  if (error) {
    return res.status(400).json({
      success: false,
      message: error!.details[0].message,
    });
  }

  try {
    const { oldPassword, newPassword, confirmPassword } = value;
    const { _id } = req.user!;
    const existingUser = await User.findById(_id);
    if (!existingUser)
      return res.status(400).json({
        success: false,
        message: 'Invalid request',
      });

    const compare = await bcrypt.compare(oldPassword, existingUser.password);
    if (!compare)
      return res.status(403).json({
        success: false,
        message: 'Old password is incorrect',
      });

    if (newPassword !== confirmPassword)
      return res.status(401).json({
        success: false,
        message: 'New passwords do not match',
      });

    existingUser.password = await bcrypt.hash(newPassword, 8);
    await existingUser.save();
    return res.status(201).json({
      success: true,
      message: 'Password change successful',
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
};
