import { Injectable } from '@nestjs/common';
import { DataSource, In, IsNull } from 'typeorm';
import { UserGroup } from './entity/user_group.entity';
import { UserGroupMember } from './entity/user_group_member.entity';
import { User } from '../users/entity/user.entity';
import { TypistGroupNotExistError, TypistIdInvalidError } from './errors/types';
import { USER_ROLES } from '../../constants';
import {
  insertEntities,
  insertEntity,
  updateEntity,
  deleteEntity,
} from '../../common/repository';
import { Context } from '../../common/log';

@Injectable()
export class UserGroupsRepositoryService {
  //クエリログにコメントを付与するかどうか
  private readonly isCommentOut = process.env.STAGE !== 'local';
  constructor(private dataSource: DataSource) {}

  async getUserGroups(
    context: Context,
    account_id: number,
  ): Promise<UserGroup[]> {
    const value = await this.dataSource.transaction(async (entityManager) => {
      const userGroupRepo = entityManager.getRepository(UserGroup);

      const userGroups = await userGroupRepo.find({
        // 論理削除されていないレコードを取得
        where: { account_id: account_id, deleted_at: IsNull() },
        comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
      });

      return userGroups;
    });
    return value;
  }
  /**
   * ユーザーグループIDからユーザー所属一覧を取得する
   * @param groupIds
   * @returns users from groups
   */
  async getGroupMembersFromGroupIds(
    context: Context,
    groupIds: number[],
  ): Promise<UserGroupMember[]> {
    return await this.dataSource.transaction(async (entityManager) => {
      const groupMemberRepo = entityManager.getRepository(UserGroupMember);

      const groupMembers = await groupMemberRepo.find({
        relations: {
          user: true,
        },
        where: {
          user_group_id: In(groupIds),
        },
        comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
      });

      return groupMembers;
    });
  }

  /**
   * タイピストグループと所属するメンバーのIDを取得します
   * @param accountId
   * @param typistGroupId
   * @returns typist group
   */
  async getTypistGroup(
    context: Context,
    accountId: number,
    typistGroupId: number,
  ): Promise<UserGroup> {
    return await this.dataSource.transaction(async (entityManager) => {
      const userGroupRepo = entityManager.getRepository(UserGroup);

      const userGroup = await userGroupRepo.findOne({
        where: {
          id: typistGroupId,
          account_id: accountId,
          deleted_at: IsNull(),
        },
        relations: {
          userGroupMembers: true,
        },
        comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
      });

      if (!userGroup) {
        throw new TypistGroupNotExistError(
          `Typist Group is not exist. typistGroupId: ${typistGroupId}`,
        );
      }
      return userGroup;
    });
  }

  /**
   * 指定したアカウントIDでタイピストグループを作成し、そのタイピストグループとtypistIdsのユーザーを紐付ける
   * @param accountId
   * @param name
   * @param typistIds
   * @returns createdTypistGroup
   */
  async createTypistGroup(
    context: Context,
    name: string,
    typistIds: number[],
    accountId: number,
  ): Promise<UserGroup> {
    return await this.dataSource.transaction(async (entityManager) => {
      const userGroupRepo = entityManager.getRepository(UserGroup);
      const userGroupMemberRepo = entityManager.getRepository(UserGroupMember);
      // typistIdsのidを持つユーザーが、account_idのアカウントに所属していて、かつ、roleがtypistであることを確認する
      const userRepo = entityManager.getRepository(User);
      const userRecords = await userRepo.find({
        where: {
          id: In(typistIds),
          account_id: accountId,
          role: USER_ROLES.TYPIST,
          email_verified: true,
        },
        comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
      });
      if (userRecords.length !== typistIds.length) {
        throw new TypistIdInvalidError(
          `Typist user not exists or email not verified Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
            (x) => x.id,
          )}`,
        );
      }
      // userGroupをDBに保存する
      const userGroup = await insertEntity(
        UserGroup,
        userGroupRepo,
        {
          name,
          account_id: accountId,
        },
        this.isCommentOut,
        context,
      );

      const userGroupMembers = userRecords.map((user) => {
        return {
          user_group_id: userGroup.id,
          user_id: user.id,
        };
      });
      // userGroupMembersをDBに保存する
      await insertEntities(
        UserGroupMember,
        userGroupMemberRepo,
        userGroupMembers,
        this.isCommentOut,
        context,
      );

      return userGroup;
    });
  }

  /**
   * 指定したIDのタイピストグループを更新し、そのタイピストグループとtypistIdsのユーザーを紐付ける
   * @param accountId
   * @param name
   * @param typistIds
   * @returns createdTypistGroup
   */
  async updateTypistGroup(
    context: Context,
    accountId: number,
    typistGroupId: number,
    typistGroupName: string,
    typistIds: number[],
  ): Promise<UserGroup> {
    return await this.dataSource.transaction(async (entityManager) => {
      const userGroupRepo = entityManager.getRepository(UserGroup);
      const userGroupMemberRepo = entityManager.getRepository(UserGroupMember);
      // typistIdsのidを持つユーザーが、account_idのアカウントに所属していて、かつ、roleがtypistであることを確認する
      const userRepo = entityManager.getRepository(User);
      const userRecords = await userRepo.find({
        where: {
          id: In(typistIds),
          account_id: accountId,
          role: USER_ROLES.TYPIST,
          email_verified: true,
        },
        comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
      });
      if (userRecords.length !== typistIds.length) {
        throw new TypistIdInvalidError(
          `Typist user not exists or email not verified Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
            (x) => x.id,
          )}`,
        );
      }

      // GroupIdが自アカウント内に存在するか確認する
      const typistGroup = await userGroupRepo.findOne({
        where: {
          id: typistGroupId,
          account_id: accountId,
        },
        comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
      });
      if (!typistGroup) {
        throw new TypistGroupNotExistError(
          `TypistGroup not exists Error. accountId: ${accountId}; typistGroupId: ${typistGroupId}`,
        );
      }

      // 対象のタイピストグループを更新する
      // ユーザーグループ名を更新する
      typistGroup.name = typistGroupName;
      await updateEntity(
        userGroupRepo,
        { id: typistGroupId },
        typistGroup,
        this.isCommentOut,
        context,
      );

      // user_group_membersテーブルから対象のタイピストグループのユーザーを削除する
      await deleteEntity(
        userGroupMemberRepo,
        {
          user_group_id: typistGroupId,
        },
        this.isCommentOut,
        context,
      );

      const typistGroupMembers = userRecords.map((typist) => {
        return {
          user_group_id: typistGroup.id,
          user_id: typist.id,
        };
      });
      await insertEntities(
        UserGroupMember,
        userGroupMemberRepo,
        typistGroupMembers,
        this.isCommentOut,
        context,
      );

      return typistGroup;
    });
  }
}
