import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import mongoose, { ObjectId } from 'mongoose';
import {
  createUserSchema,
  getUserSchema,
  editUserSchema,
  activateSchema,
  batchActivateSchema,
  saveDeviceTokenSchema,
  getDeviceTokensSchema,
} from '../validators/authValidator';
import { User } from '../models';
import { IUserInfo } from '../utils/types';
import { verifyAccess } from '../middlewares/authentication';
import { SEARCH_QUERY, UserTypes } from '../utils/constants';
import { sendCredentialsMail } from '../utils/email';

export const createNewUser = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  const { company_id, company_name } = req.user!;
  const { error, value } = createUserSchema.body.validate(req.body);

  if (error) {
    return res.status(400).json({
      success: false,
      message: error.details[0].message,
    });
  }
  const { userType, password, email, phoneNumber, firstName } = value;

  //check if email already exist
  const checkEmail = await User.findOne({ email: email });
    if (checkEmail) {
      return res.status(409).json({
        success: false,
        message: "A user with email ("+email+") already exists",
      });
    } 

  //check if phone number already exist
  const checkPhone = await User.findOne({ phoneNumber: phoneNumber });
  if (checkPhone) {
    return res.status(409).json({
      success: false,
      message: "A user with the phone number ("+phoneNumber+") already exists",
    });
  } 

  if (userType === UserTypes.STAFF) {
    const validAccess = await verifyAccess(req, 'create_users');
    if (!validAccess) {
      return res.status(403).json({
        success: false,
        message: 'Access denied',
      });
    }
  }

  if (userType === UserTypes.RIDER) {
    const validAccess = await verifyAccess(req, 'create_dispatchers');
    if (!validAccess) {
      return res.status(403).json({
        success: false,
        message: 'Access denied',
      });
    }
  }
  const hashedPass = await bcrypt.hash(password, 8);
  const userData: IUserInfo = {
    ...value,
    company_id,
    company_name,
    isCompany: false,
    isActive: true,
    userType,
    password: hashedPass,
  };
  const payload = {
    firstName: firstName,
    email: email,
  };

  try {
    await User.create(userData);
    await sendCredentialsMail(payload, password);
    return res.status(201).json({
      success: true,
      message: `${userType} created successfully`,
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
};

export const editUser = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  const { company_id: companyID } = req.user!;
  const { error: errorParams, value: valueParams } =
    editUserSchema.params.validate(req.params);

  const { error: errorBody, value: valueBody } = editUserSchema.body.validate(
    req.body,
  );

  if (errorParams || errorBody) {
    return res.status(400).json({
      success: false,
      message: errorParams
        ? errorParams.details[0].message
        : errorBody!.details[0].message,
    });
  }
  const { userType: type, userID } = valueParams as {
    userType: string;
    userID: ObjectId;
  };

  try {
    const { role_id, address } = valueBody;
    if (type === UserTypes.STAFF) {
      if (address) {
        return res.status(400).json({
          success: false,
          message: 'address is not not allowed',
        });
      }
      const validAccess = await verifyAccess(req, 'create_users');
      if (!validAccess) {
        return res.status(403).json({
          success: false,
          message: 'Access denied',
        });
      }
    }

    if (type === UserTypes.RIDER) {
      if (role_id) {
        return res.status(400).json({
          success: false,
          message: 'Role id is not not allowed',
        });
      }
      const validAccess = await verifyAccess(req, 'create_dispatchers');
      if (!validAccess) {
        return res.status(403).json({
          success: false,
          message: 'Access denied',
        });
      }
    }

    const user = await User.findOne({
      company_id: companyID,
      userType: type,
      _id: userID,
    });

    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not Found',
      });
    }

    user.set({
      ...valueBody,
    });

    await user.save();
    return res.status(201).json({
      success: true,
      message: `${type} updated successfully`,
      data: {
        user,
      },
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
};

export const getUsers = async (
  req: Request,
  res: Response,
): Promise<Response> => {
  const { company_id } = req.user!;
  const { error: errorParams, value: valueParams } =
    getUserSchema.params.validate(req.params);
  const { error: errorQuery, value: valueQuery } = getUserSchema.query.validate(
    req.query,
  );

  if (errorParams || errorQuery) {
    return res.status(400).json({
      success: false,
      message: errorParams
        ? errorParams.details[0].message
        : errorQuery!.details[0].message,
    });
  }
  const { userType: type } = valueParams as { userType: string };
  const { userID, sort, isActive, userType, keyword, limit, page } = valueQuery;

  const selector = {
    company_id,
  };

  let sortBy: string = 'createdAt';
  let sortOrder: string = 'desc';
  if (sort !== undefined){
    sortBy = sort.field ? sort.field : sortBy;
    sortOrder = sort.sort ? sort.sort : sortOrder;
  }
  const options = {
    limit: SEARCH_QUERY.LIMIT,
    sort: [[sortBy, sortOrder]],
    // sort: { createdAt: 'desc' },
    populate: 'role_id',
  };

  if (type === UserTypes.STAFF) {
    Object.assign(selector, { userType: type });
    const validAccess = await verifyAccess(req, 'view_users');
    if (!validAccess) {
      return res.status(403).json({
        success: false,
        message: 'Access denied',
      });
    }
  }

  if (type === UserTypes.RIDER) {
    const validAccess = await verifyAccess(req, 'view_dispatchers');
    Object.assign(selector, { userType: type });
    if (!validAccess) {
      return res.status(403).json({
        success: false,
        message: 'Access denied',
      });
    }
  }

  if (type === 'all') {
    const staffAccess = verifyAccess(req, 'view_users');
    const riderAccess = verifyAccess(req, 'view_dispatchers');
    const adminAccess = await Promise.all([staffAccess, riderAccess]);
    const validAccess = adminAccess.every((access) => access === true);
    if (validAccess) {
      Object.assign(selector, { isCompany: false });
    } else if (adminAccess[0]) {
      Object.assign(selector, { userType: UserTypes.STAFF });
    } else if (adminAccess[1]) {
      Object.assign(selector, { userType: UserTypes.RIDER });
    } else {
      return res.status(403).json({
        success: false,
        message: 'Access denied',
      });
    }
  }

  try {
    //Quering
    if (userID) {
      Object.assign(selector, { _id: userID });
    }
    if (isActive) {
      let isActiveOpt: boolean;
      isActive === 'true' ? isActiveOpt = true : isActiveOpt = false;
      Object.assign(selector, { isActive: isActiveOpt });
    }
    if (userType) {
      Object.assign(selector, { userType: userType });
    }
    //Query the user's first name, last name, email, phone number and address by keyword 
    if (keyword) {
      Object.assign(selector, {
        $or:[
          {
            firstName: new RegExp(`^.*${keyword}.*$`, 'i')
          },
          {
            lastName: new RegExp(`^.*${keyword}.*$`, 'i')
          }, 
          {
            email: keyword
          },
          {
            phoneNumber: keyword
          },
          {
            address: new RegExp(`^.*${keyword}.*$`, 'i')
          }
        ]
      });
    }
    if (limit) {
      Object.assign(options, { limit });
    }
    if (page) {
      Object.assign(options, { page });
    }

    const staffsPromise = User.paginate(selector, options);
    const riderCountPromise = User.aggregate([
      {
        $match: {
          company_id: new mongoose.Types.ObjectId(
            company_id as unknown as string,
          ),
          userType: UserTypes.RIDER,
        },
      },
      {
        $group: {
          _id: '$isActive',
          count: { $sum: 1 },
        },
      },
    ]);

    const queriesResolved = await Promise.all([
      staffsPromise,
      riderCountPromise,
    ]);
    const staffsInfo = queriesResolved[0];
    const staffs = staffsInfo.docs;
    const riderCountInfo = queriesResolved[1];
    const stats = riderCountInfo.reduce(
      (acc, item) => {
        acc[item._id] = item.count;
        acc.total += item.count;
        return acc;
      },
      {
        total: 0,
        true: 0,
        false: 0,
      },
    );

    if (userID && !staffs.length) {
      return res.status(404).json({
        success: false,
        message: 'User not Found',
      });
    }

    const response = {
      success: true,
      message: userID ? 'Get staff successful' : 'Get all staff successful',
      data: {
        users: userID ? staffs[0] : staffs,
        limit: staffsInfo.limit,
        hasPrevPage: staffsInfo.hasPrevPage,
        hasNextPage: staffsInfo.hasNextPage,
        totalData: staffsInfo.totalDocs,
        currentPage: staffsInfo.page,
        totalPages: staffsInfo.totalPages,
      },
    };
    if (type === UserTypes.RIDER) {
      Object.assign(response.data, { stats });
    }
    return res.status(200).json(response);
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
};

export const deactivateOrActivateUser = async (req: Request, res: Response) => {
  const { company_id } = req.user!;
  const { error: errorParams, value: valueParams } =
    activateSchema.params.validate(req.params);
  const { error: errorQuery, value: valueQuery } =
    activateSchema.query.validate(req.query);

  if (errorParams || errorQuery) {
    return res.status(400).json({
      success: false,
      message: errorParams
        ? errorParams.details[0].message
        : errorQuery!.details[0].message,
    });
  }
  const { userID, userType } = valueParams;
  const { action } = valueQuery;
  if (userType === UserTypes.RIDER) {
    const validAccess = await verifyAccess(req, 'activate_dispatcher');
    if (!validAccess) {
      return res.status(403).json({
        success: false,
        message: 'Access denied',
      });
    }
  }
  if (userType === UserTypes.STAFF) {
    const validAccess = await verifyAccess(req, 'activate_staff');
    if (!validAccess) {
      return res.status(403).json({
        success: false,
        message: 'Access denied',
      });
    }
  }
  const user = await User.findOne({
    company_id,
    _id: userID,
    userType,
  });
  if (!user)
    return res.status(404).json({
      success: false,
      message: 'User does not exist',
    });

  if (action === 'activate') {
    if (user.isActive)
      return res.status(401).json({
        success: false,
        message: 'User is already activated',
      });
    user.isActive = true;
    await user.save();
    return res.status(201).json({
      success: true,
      message: 'User activated successfully',
    });
  }

  if (!user.isActive)
    return res.status(401).json({
      success: false,
      message: 'User is already deactivated',
    });
  user.isActive = false;
  await user.save();
  return res.status(201).json({
    success: true,
    message: 'User deactivated successfully',
  });
};

export const batchDeactivateOrActivateUser = async (
  req: Request,
  res: Response,
) => {
  const { company_id } = req.user!;
  const { error: errorParams, value: valueParams } =
    batchActivateSchema.params.validate(req.params);
  const { error: errorBody, value: valueBody } =
    batchActivateSchema.body.validate(req.body);

  if (errorParams || errorBody) {
    return res.status(400).json({
      success: false,
      message: errorParams
        ? errorParams.details[0].message
        : errorBody!.details[0].message,
    });
  }
  const { action } = valueParams;
  const { userID } = valueBody;
  const actionType = action === 'activate';

  await User.updateMany(
    { _id: { $in: userID }, company_id },
    { isActive: actionType },
  );

  return res.status(201).json({
    success: true,
    message: `Users ${action}d successfully`,
  });
};

export const saveDeviceToken = async (
  req: Request,
  res: Response,
) => {
  const { error: errorBody, value: valueBody } = saveDeviceTokenSchema.body.validate(req.body);
  if (errorBody) {
    return res.status(400).json({
      success: false,
      message: errorBody!.details[0].message
    });
  }
  try {
    const user = await User.findById(req.user!._id);
    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not Found',
      });
    }

  
    let arrayAndroidTokens: string[] = [];
    let arrayIosTokens: string[] = [];
    //get the existing device token for the user if exist
    if (user.deviceTokens !== undefined && user.deviceTokens.android !== undefined && user.deviceTokens.android.length > 0){
      arrayAndroidTokens = user.deviceTokens.android;
    }
    if (user.deviceTokens !== undefined && user.deviceTokens.ios !== undefined && user.deviceTokens.ios.length > 0){
      arrayIosTokens = user.deviceTokens.ios;
    } 

    if (valueBody.deviceToken.device === 'android'){
      //append the new device token to the exisitng device tokens
      arrayAndroidTokens.push(valueBody.deviceToken.token)
       //remove the first token in the array if the tokens are greater than 5
      if(arrayAndroidTokens.length > 5){
        arrayAndroidTokens.shift();
      }
    } else {
      //append the new device token to the exisitng device tokens
      arrayIosTokens.push(valueBody.deviceToken.token)
      //remove the first token in the array if the tokens are greater than 5
      if(arrayIosTokens.length > 5){
        arrayIosTokens.shift();
      }
    }
    user.set({
      'deviceTokens': {
        'android': Array.from(new Set(arrayAndroidTokens)),
        'ios': Array.from(new Set(arrayIosTokens)),
      }
    });
    await user.save();
    return res.status(201).json({
      success: true,
      message: `User device saved successfully`,
      data: {
        user,
      },
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
};

export const getUserDeviceTokens = async (
  req: Request,
  res: Response,
) => {
  const { error: errorParams, value: valueParams } = getDeviceTokensSchema.params.validate(req.params);
  if (errorParams) {
    return res.status(400).json({
      success: false,
      message: errorParams!.details[0].message
    });
  }
  try {
    const user = await User.findById(valueParams.userID);
    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not Found',
      });
    }
    return res.status(201).json({
      success: true,
      message: `User Device Tokens Retrived Successfully`,
      data: user.deviceTokens,
    });
  } catch (err: any) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
};


