import { SelectQueryBuilder } from "typeorm";
import { dataSource } from "../../../data-source";
import { member } from "../../../entity/club/member/member";
import { membership } from "../../../entity/club/member/membership";
import DatabaseActionException from "../../../exceptions/databaseActionException";
import InternalException from "../../../exceptions/internalException";
import { memberView } from "../../../views/memberView";
import { DB_TYPE } from "../../../env.defaults";

export default abstract class MemberService {
  /**
   * @description get all members
   * @returns {Promise<[Array<member>, number]>}
   */
  static async getAll({
    offset = 0,
    count = 25,
    search = "",
    noLimit = false,
    ids = [],
  }: {
    offset?: number;
    count?: number;
    search?: string;
    noLimit?: boolean;
    ids?: Array<string>;
  }): Promise<[Array<member>, number]> {
    let query = this.applyMemberBaseJoins();

    if (search != "") {
      search.split(" ").forEach((term, index) => {
        const searchQuery = `%${term}%`;
        const dynamic = "searchQuery" + Math.random().toString(36).substring(2);
        if (index == 0) {
          query = query.where(`member.firstname LIKE :${dynamic} OR member.lastname LIKE :${dynamic}`, {
            [dynamic]: searchQuery,
          });
        } else {
          query = query.orWhere(`member.firstname LIKE :${dynamic} OR member.lastname LIKE :${dynamic}`, {
            [dynamic]: searchQuery,
          });
        }
      });
    }

    if (ids.length != 0) {
      query = query.where("member.id IN (:...ids)", { ids: ids });
    }

    if (!noLimit) {
      query = query.offset(offset).limit(count);
    }

    return await query
      .orderBy("member.lastname")
      .addOrderBy("member.firstname")
      .addOrderBy("member.nameaffix")
      .getManyAndCount()
      .then((res) => {
        return res;
      })
      .catch((err) => {
        throw new DatabaseActionException("SELECT", "member", err);
      });
  }

  /**
   * @description get member by id
   * @param {string} id
   * @returns {Promise<member>}
   */
  static async getById(id: string): Promise<member> {
    return this.applyMemberBaseJoins()
      .where("member.id = :id", { id: id })
      .getOneOrFail()
      .then((res) => {
        return res;
      })
      .catch((err) => {
        throw new DatabaseActionException("SELECT", "member", err);
      });
  }

  /**
   * @description get member statistics by id
   * @param {string} id
   * @returns {Promise<memberView>}
   */
  static async getStatisticsById(id: string): Promise<memberView> {
    return await dataSource
      .getRepository(memberView)
      .createQueryBuilder("memberView")
      .where("memberView.id = :id", { id: id })
      .getOneOrFail()
      .then((res) => {
        return res;
      })
      .catch((err) => {
        throw new DatabaseActionException("SELECT", "memberView", err);
      });
  }

  /**
   * @description get members where membership is setz
   * @returns {Promise<member>}
   */
  static async getByRunningMembership(): Promise<Array<member>> {
    return await dataSource
      .getRepository(member)
      .createQueryBuilder("member")
      .leftJoinAndSelect("member.memberships", "membership")
      .where("membership.end IS NULL")
      .orderBy("member.lastname")
      .addOrderBy("member.firstname")
      .addOrderBy("member.nameaffix")
      .getMany()
      .then((res) => {
        return res;
      })
      .catch((err) => {
        throw new DatabaseActionException("SELECT", "member", err);
      });
  }

  /**
   * @description get newsletter by member by id
   * @param {string} id
   * @returns {Promise<member>}
   */
  static async getNewsletterById(id: string): Promise<member> {
    return await dataSource
      .getRepository(member)
      .createQueryBuilder("member")
      .leftJoinAndMapOne(
        "member.sendNewsletter",
        "member.communications",
        "sendNewsletter",
        "sendNewsletter.isSendNewsletter = 1"
      )
      .where("member.id = :id", { id: id })
      .getOneOrFail()
      .then((res) => {
        return res;
      })
      .catch((err) => {
        throw new DatabaseActionException("SELECT", "member", err);
      });
  }

  /**
   * @description apply member joins to query
   * @returns {SelectQueryBuilder<member>}
   */
  static applyMemberBaseJoins(): SelectQueryBuilder<member> {
    return dataSource
      .getRepository(member)
      .createQueryBuilder("member")
      .leftJoinAndMapOne(
        "member.firstMembershipEntry",
        "member.memberships",
        "membership_first",
        DB_TYPE == "postgres"
          ? 'membership_first.memberId = member.id AND membership_first.start = (SELECT MIN("m_first"."start") FROM "membership" "m_first" WHERE "m_first"."memberId" = "member"."id")'
          : "membership_first.memberId = member.id AND membership_first.start = (SELECT MIN(m_first.start) FROM membership m_first WHERE m_first.memberId = member.id)"
      )
      .leftJoinAndMapOne(
        "member.lastMembershipEntry",
        "member.memberships",
        "membership_last",
        DB_TYPE == "postgres"
          ? 'membership_last.memberId = member.id AND membership_last.start = (SELECT MAX("m_last"."start") FROM "membership" "m_last" WHERE "m_last"."memberId" = "member"."id")'
          : "membership_last.memberId = member.id AND membership_last.start = (SELECT MAX(m_last.start) FROM membership m_last WHERE m_last.memberId = member.id)"
      )
      .leftJoinAndSelect("membership_first.status", "status_first")
      .leftJoinAndSelect("membership_last.status", "status_last")
      .leftJoinAndMapMany(
        "member.preferredCommunication",
        "member.communications",
        "preferredCommunication",
        "preferredCommunication.preferred = true"
      )
      .leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred")
      .leftJoinAndMapOne(
        "member.sendNewsletter",
        "member.communications",
        "sendNewsletter",
        "sendNewsletter.isSendNewsletter = true"
      )
      .leftJoinAndSelect("sendNewsletter.type", "communicationtype")
      .leftJoinAndMapMany(
        "member.smsAlarming",
        "member.communications",
        "smsAlarming",
        "smsAlarming.isSMSAlarming = true"
      )
      .leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming")
      .leftJoinAndSelect("member.salutation", "salutation");
  }
}