import { Request, Response } from "express";
import { getIO } from "../libs/socket";
import { Op } from "sequelize";
import CreateUserService from "../services/UserServices/CreateUserService";
import UpdateUserService from "../services/UserServices/UpdateUserService";
import DeleteUserService from "../services/UserServices/DeleteUserService";
import { del, get, set } from "../helpers/RedisClient";

import {
  startWhoIsOnlineMonitor,
  stopWhoIsOnlineMonitor
} from "../helpers/WhoIsOnlineMonitor";

import User from "../models/User";

export const createUser = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const { user_id, user_first_name, user_tax_id, user_email, user_title }: any =
    req.body;

  const invalid = invalidProperties(req.body, [
    "user_id",
    "user_tax_id",
    "user_first_name"
  ]);

  if (invalid) {
    return res.status(400).json(response("1", `${invalid}`, "0", "createUser"));
  }

  const auxUser = await User.findOne({ where: { secondaryId: user_id } });

  if (auxUser) {
    return res
      .status(400)
      .json(
        response("1", `The user ${user_id} already exist`, "0", "createUser")
      );
  }

  const user = await CreateUserService({
    email: user_tax_id || user_email,
    password: "12345",
    name: user_first_name, 
    profile: "user",
    ignoreThrow: true
  });

  if (user?.error) {
    return res
      .status(user?.status)
      .json(response("0", `${user?.msg}`, "0", "createUser"));
  }

  if (!user?.error) {
    const _user = await User.findByPk(user.id);
    _user?.update({ secondaryId: user_id });

    const { id, name } = user;
    await set(`user:${id}`, { id, name });

    const io = getIO();
    io.emit("user", {
      action: "create",
      user
    });

    await startWhoIsOnlineMonitor();
  }

  return res
    .status(200)
    .json(response("1", `User ${user_id} created`, "1", "createUser"));
};

export const deleteUser = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const { user_id }: any = req.body;

  const invalid = invalidProperties(req.body, ["user_id"]);

  if (invalid) {
    return res.status(400).json(response("1", `${invalid}`, "0", "deleteUser"));
  }

  const _user = await User.findOne({ where: { secondaryId: user_id } });

  if (_user) {
    const user = await DeleteUserService(_user.id, true);

    if (user?.error) {
      return res
        .status(user?.status)
        .json(response("0", `${user?.msg}`, "0", "deleteUser"));
    }

    if (!user?.error) {
      del(`user:${_user.id}`);

      const io = getIO();
      io.emit("user", {
        action: "delete",
        userId: _user.id
      });

      await stopWhoIsOnlineMonitor();

      io.emit("onlineStatus", {
        action: "delete",
        userOnlineTime: _user.id
      });

      await startWhoIsOnlineMonitor();

      return res
        .status(200)
        .json(response("1", `User ${user_id} deleted`, "1", "deleteUser"));
    }
  }

  return res
    .status(500)
    .json(response("0", "Internal server error", "0", "deleteUser"));
};

export const listAllUsers = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const _users: any = await User.findAll({
    where: {
      secondaryId: {
        [Op.ne]: ""
      }
    },
    attributes: ["secondaryId", "name"]
  });

  if (_users) {
    const user_list = _users.map((user: any) => {
      const { secondaryId, name } = user;
      return { user_id: secondaryId, full_name: name };
    });

    return res
      .status(200)
      .json(response("1", "Success", user_list, "listAllUsers"));
  }

  return res
    .status(500)
    .json(response("0", "Internal server error", [], "listAllUsers"));
};

export const checkUser = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const { user_id }: any = req.body;

  const invalid = invalidProperties(req.body, ["user_id"]);

  if (invalid) {
    return res.status(400).json(response("1", `${invalid}`, "0", "checkUser"));
  }

  const _user = await User.findOne({ where: { secondaryId: user_id } });

  if (_user) {
    return res
      .status(200)
      .json(response("1", `User ${user_id} exist`, "1", "checkUser"));
  }

  return res
    .status(404)
    .json(response("1", `User ${user_id} not exist`, "0", "checkUser"));
};

export const updateUser = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const { user_id, user_first_name, user_tax_id, user_email, user_title }: any =
    req.body;

  const invalid = invalidProperties(req.body, ["user_id"]);

  if (invalid) {
    return res.status(400).json(response("1", `${invalid}`, "0", "checkUser"));
  }

  const _user: any = await User.findOne({ where: { secondaryId: user_id } });

  if (!_user)
    return res
      .status(404)
      .json(response("1", `User ${user_id} not exist`, "0", "updateUser"));

  const userData = {
    email: user_tax_id || user_email,
    name: user_first_name, 
  };

  let user: any = await UpdateUserService({
    userData,
    userId: _user.id,
    ignoreThrow: true
  });

  if (user?.error) {
    return res
      .status(user?.status)
      .json(response("0", `${user?.msg}`, "0", "updateUser"));
  }

  if (user) {
    const { id, name } = user;
    await set(`user:${id}`, { id, name });
  }

  const io = getIO();
  io.emit("user", {
    action: "update",
    user
  });

  return res
    .status(200)
    .json(response("1", `User ${user_id} updated`, "1", "updateUser"));
};

export const resetPassword = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const { user_id, user_password }: any = req.body;

  const invalid = invalidProperties(req.body, ["user_id", "user_password"]);

  if (invalid) {
    return res
      .status(400)
      .json(response("1", `${invalid}`, "0", "resetPassword"));
  }

  const _user = await User.findOne({ where: { secondaryId: user_id } });

  if (!_user) {
    return res
      .status(404)
      .json(response("1", `User ${user_id} not exist`, "0", "resetPassword"));
  }

  const userData = {
    password: user_password,
    email: _user.email
  };

  let user: any = await UpdateUserService({
    userData,
    userId: _user.id,
    ignoreThrow: true
  });

  if (user?.error) {
    return res
      .status(user?.status)
      .json(response("0", `${user?.msg}`, "0", "resetPassword"));
  }

  await logoutUser(_user.id);

  return res
    .status(200)
    .json(
      response("1", `User ${user_id} password updated`, "1", "resetPassword")
    );
};

export const linkUserAndUserRight = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const { user_id, user_right_id, user_right_title }: any = req.body;

  const invalid = invalidProperties(req.body, ["user_id", "user_right_id"]);

  if (invalid) {
    return res
      .status(400)
      .json(response("1", `${invalid}`, "0", "linkUserAndUserRight"));
  }

  if (
    (user_right_id &&
      !["admin", "user", "supervisor"].includes(
        user_right_id?.trim().toLocaleLowerCase()
      )) ||
    (user_right_title &&
      !["admin", "user", "supervisor"].includes(
        user_right_title?.trim().toLocaleLowerCase()
      ))
  ) {
    return res
      .status(400)
      .json(
        response(
          "1",
          `The user profile ${
            user_right_title || user_right_id
          } provided by the property user_right_title or user_right_id does not match the following profiles: admin, user, supervisor`,
          "0",
          "linkUserAndUserRight"
        )
      );
  }

  const _user: any = await User.findOne({ where: { secondaryId: user_id } });

  if (!_user)
    return res
      .status(404)
      .json(
        response("1", `User ${user_id} not exist`, "0", "linkUserAndUserRight")
      );

  const userData = {
    profile: user_right_title || user_right_id,
    email: _user.email
  };

  let user: any = await UpdateUserService({
    userData,
    userId: _user.id,
    ignoreThrow: true
  });

  if (user?.error) {
    return res
      .status(user?.status)
      .json(response("0", `${user?.msg}`, "0", "linkUserAndUserRight"));
  }

  await logoutUser(_user.id);

  return res
    .status(200)
    .json(
      response(
        "1",
        `User ${user_id} associated with ${
          user_right_title || user_right_id
        } profile`,
        "1",
        "linkUserAndUserRight"
      )
    );
};

export const checkUserRight = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const { user_id, user_right_id, user_right_title }: any = req.body;

  const invalid = invalidProperties(req.body, ["user_id", "user_right_id"]);

  if (invalid) {
    return res
      .status(400)
      .json(response("1", `${invalid}`, "0", "checkUserRight"));
  }

  if (
    (user_right_id &&
      !["admin", "user", "supervisor"].includes(
        user_right_id?.trim().toLocaleLowerCase()
      )) ||
    (user_right_title &&
      !["admin", "user", "supervisor"].includes(
        user_right_title?.trim().toLocaleLowerCase()
      ))
  ) {
    return res
      .status(400)
      .json(
        response(
          "1",
          `The user profile ${
            user_right_title || user_right_id
          } provided by the property user_right_title or user_right_id does not match the following profiles: admin, user, supervisor`,
          "0",
          "checkUserRight"
        )
      );
  }

  const _user: any = await User.findOne({
    where: {
      secondaryId: user_id
    }
  });

  if (!_user)
    return res
      .status(404)
      .json(response("1", `User ${user_id} not exist`, "0", "checkUserRight"));

  if (
    (user_right_id && _user.profile != user_right_id) ||
    (user_right_title && _user.profile != user_right_title)
  ) {
    return res
      .status(403)
      .json(
        response(
          "1",
          `User ${user_id} does not have this profile`,
          "0",
          "checkUserRight"
        )
      );
  }

  return res
    .status(200)
    .json(
      response(
        "1",
        `User ${user_id} has ${user_right_title || user_right_id} profile`,
        "1",
        "checkUserRight"
      )
    );
};

async function logoutUser(userId: any) {
  await stopWhoIsOnlineMonitor();

  let onlineTime = {
    userId: `${userId}`,
    status: "logout..."
  };

  const io = getIO();
  io.emit("onlineStatus", {
    action: "logout",
    userOnlineTime: onlineTime
  });

  await startWhoIsOnlineMonitor();
}

function response(code: string, msg: string, obj: any, type: string) {
  let payload = { return_code: code, return_msg: msg };

  switch (type) {
    case "createUser":
      return { ...payload, user_created: obj };
    case "deleteUser":
      return { ...payload, user_removed: obj };
    case "listAllUsers":
      return { ...payload, user_list: obj };
    case "checkUser":
      return { ...payload, user_exists: obj };
    case "updateUser":
      return { ...payload, user_updated: obj };
    case "resetPassword":
      return { ...payload, password_set: obj };
    case "linkUserAndUserRight":
      return { ...payload, user_right_linked: obj };
    case "checkUserRight":
      return { ...payload, user_right_exists: obj };
    default:
      return payload;
  }
}

function invalidProperties(body: any, pros: any[]) {
  for (const field of pros) {
    console.log("body[field]: ", body[field], " field: ", field);
    if (!body[field]) {
      return `${field} is required`;
    }
  }
  return false;
}
