import {IWorkLenzRequest} from "../interfaces/worklenz-request";
import {IWorkLenzResponse} from "../interfaces/worklenz-response";

import db from "../config/db";
import {ServerResponse} from "../models/server-response";
import WorklenzControllerBase from "./worklenz-controller-base";
import HandleExceptions from "../decorators/handle-exceptions";
import {getColor} from "../shared/utils";
import TeamMembersController from "./team-members-controller";
import {checkTeamSubscriptionStatus} from "../shared/paddle-utils";
import {updateUsers} from "../shared/paddle-requests";
import {statusExclude} from "../shared/constants";
import {NotificationsService} from "../services/notifications/notifications.service";

export default class ProjectMembersController extends WorklenzControllerBase {

  public static async checkIfUserAlreadyExists(owner_id: string, email: string) {
    if (!owner_id) throw new Error("Owner not found.");

    const q = `SELECT EXISTS(SELECT tmi.team_member_id
              FROM team_member_info_view AS tmi
                       JOIN teams AS t ON tmi.team_id = t.id
              WHERE tmi.email = $1::TEXT
                AND t.user_id = $2::UUID);`;
    const result = await db.query(q, [email, owner_id]);

    const [data] = result.rows;
    return data.exists;
  }

  public static async createOrInviteMembers(body: any) {
    if (!body) return;

    const q = `SELECT create_project_member($1) AS res;`;

    const result = await db.query(q, [JSON.stringify(body)]);
    const [data] = result.rows;

    const response = data.res;

    if (response?.notification && response?.member_user_id) {
      NotificationsService.sendNotification({
        receiver_socket_id: response.socket_id,
        project: response.project,
        message: response.notification,
        project_color: response.project_color,
        project_id: response.project_id,
        team: response.team,
        team_id: body.team_id
      });
    }
    return data;
  }

  @HandleExceptions()
  public static async create(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    req.body.user_id = req.user?.id;
    req.body.team_id = req.user?.team_id;
    req.body.access_level = req.body.access_level ? req.body.access_level : "MEMBER";
    const data = await this.createOrInviteMembers(req.body);
    return res.status(200).send(new ServerResponse(true, data));
  }

  @HandleExceptions({
    raisedExceptions: {
      "ERROR_EMAIL_INVITATION_EXISTS": "Member already have a pending invitation that has not been accepted."
    }
  })
  public static async createByEmail(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    req.body.user_id = req.user?.id;
    req.body.team_id = req.user?.team_id;

    if (!req.user?.team_id) return res.status(200).send(new ServerResponse(false, "Required fields are missing."));

    // check the subscription status
    const subscriptionData = await checkTeamSubscriptionStatus(req.user?.team_id);

    const userExists = await this.checkIfUserAlreadyExists(req.user?.owner_id as string, req.body.email);

    // Return error if user already exists
    if (userExists) {
      return res.status(200).send(new ServerResponse(false, null, "User already exists in the team."));
    }

    // Handle self-hosted subscriptions differently
    if (subscriptionData.subscription_type === 'SELF_HOSTED') {
      // Adding as a team member
      const teamMemberReq: { team_id?: string; emails: string[], project_id?: string; } = {
        team_id: req.user?.team_id,
        emails: [req.body.email]
      };

      if (req.body.project_id)
        teamMemberReq.project_id = req.body.project_id;

      const [member] = await TeamMembersController.createOrInviteMembers(teamMemberReq, req.user);

      if (!member)
        return res.status(200).send(new ServerResponse(true, null, "Failed to add the member to the project. Please try again."));

      // Adding to the project
      const projectMemberReq = {
        team_member_id: member.team_member_id,
        team_id: req.user?.team_id,
        project_id: req.body.project_id,
        user_id: req.user?.id,
        access_level: req.body.access_level ? req.body.access_level : "MEMBER"
      };
      const data = await this.createOrInviteMembers(projectMemberReq);
      return res.status(200).send(new ServerResponse(true, data.member));
    }

    if (statusExclude.includes(subscriptionData.subscription_status)) {
      return res.status(200).send(new ServerResponse(false, null, "Unable to add user! Please check your subscription status."));
    }

    if (!userExists && subscriptionData.is_ltd && subscriptionData.current_count && (parseInt(subscriptionData.current_count) + 1 > parseInt(subscriptionData.ltd_users))) {
      return res.status(200).send(new ServerResponse(false, null, "Maximum number of life time users reached."));
    }

    // if (subscriptionData.status === "trialing") break;
    if (!userExists && !subscriptionData.is_credit && !subscriptionData.is_custom && subscriptionData.subscription_status !== "trialing") {
      // if (subscriptionData.subscription_status === "active") {
      //   const response = await updateUsers(subscriptionData.subscription_id, (subscriptionData.quantity + 1));
      //   if (!response.body.subscription_id) return res.status(200).send(new ServerResponse(false, null, response.message || "Unable to add user! Please check your subscription."));
      // }
      const updatedCount = parseInt(subscriptionData.current_count) + 1;
      const requiredSeats = updatedCount - subscriptionData.quantity;
      if (updatedCount > subscriptionData.quantity) {
        const obj = { 
          seats_enough: false, 
          required_count: requiredSeats,
          current_seat_amount: subscriptionData.quantity
        };
        return res.status(200).send(new ServerResponse(false, obj, null));
      }
    }

    // Adding as a team member
    const teamMemberReq: { team_id?: string; emails: string[], project_id?: string; } = {
      team_id: req.user?.team_id,
      emails: [req.body.email]
    };

    if (req.body.project_id)
      teamMemberReq.project_id = req.body.project_id;

    const [member] = await TeamMembersController.createOrInviteMembers(teamMemberReq, req.user);

    if (!member)
      return res.status(200).send(new ServerResponse(true, null, "Failed to add the member to the project. Please try again."));

    // Adding to the project
    const projectMemberReq = {
      team_member_id: member.team_member_id,
      team_id: req.user?.team_id,
      project_id: req.body.project_id,
      user_id: req.user?.id,
      access_level: req.body.access_level ? req.body.access_level : "MEMBER"
    };
    const data = await this.createOrInviteMembers(projectMemberReq);
    return res.status(200).send(new ServerResponse(true, data.member));
  }

  @HandleExceptions()
  public static async get(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    const q = `
      SELECT project_members.id,
             tm.id AS team_member_id,
             (SELECT email FROM team_member_info_view WHERE team_member_info_view.team_member_id = tm.id),
             (SELECT name FROM team_member_info_view WHERE team_member_id = project_members.team_member_id) AS name,
             u.avatar_url,
             jt.name AS job_title
      FROM project_members
             INNER JOIN team_members tm ON project_members.team_member_id = tm.id
             LEFT JOIN job_titles jt ON tm.job_title_id = jt.id
             LEFT JOIN users u ON tm.user_id = u.id
      WHERE project_id = $1
      ORDER BY project_members.created_at DESC;
    `;
    const result = await db.query(q, [req.params.id]);

    result.rows.forEach((a: any) => a.color_code = getColor(a.name));

    return res.status(200).send(new ServerResponse(true, result.rows));
  }

  public static async checkIfMemberExists(projectId: string, teamMemberId: string) {
    const q = `SELECT EXISTS(SELECT id FROM project_members WHERE project_id = $1::UUID AND team_member_id = $2::UUID)`;
    const result = await db.query(q, [projectId, teamMemberId]);
    const [data] = result.rows;
    return data.exists;
  }

  @HandleExceptions()
  public static async deleteById(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    const q = `SELECT remove_project_member($1, $2, $3) AS res;`;
    const result = await db.query(q, [req.params.id, req.user?.id, req.user?.team_id]);
    const [data] = result.rows;

    const response = data.res;

    if (response?.notification && response?.member_user_id) {
      NotificationsService.sendNotification({
        receiver_socket_id: response.socket_id,
        project: response.project,
        message: response.notification,
        project_color: response.project_color,
        project_id: response.project_id,
        team: response.team,
        team_id: req.user?.team_id as string
      });
    }

    return res.status(200).send(new ServerResponse(true, result.rows));
  }
}
