import { Injectable } from '@nestjs/common';
import { DataSource, IsNull, UpdateResult } from 'typeorm';
import { User } from './entity/user.entity';
import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity';
import {
  getDirection,
  getTaskListSortableAttribute,
} from '../../common/types/sort/util';
import { UserNotFoundError, EmailAlreadyVerifiedError } from './errors/types';
import { USER_ROLES } from '../../constants';

@Injectable()
export class UsersRepositoryService {
  constructor(private dataSource: DataSource) {}

  async create(
    accountId: number,
    externalUserId: string,
    role: string,
    acceptedTermsVersion: string,
  ): Promise<User> {
    const user = new User();
    {
      user.account_id = accountId;
      user.external_id = externalUserId;
      user.role = role;
      user.accepted_terms_version = acceptedTermsVersion;
    }

    const createdEntity = await this.dataSource.transaction(
      async (entityManager) => {
        const repo = entityManager.getRepository(User);
        const newUser = repo.create(user);
        const persisted = await repo.save(newUser);
        return persisted;
      },
    );
    return createdEntity;
  }

  /**
   * 一般ユーザを作成する
   * @param account_id
   * @param external_id
   * @param role
   * @param autoRenew
   * @param licenseAlert
   * @param notification
   * @param authorId
   * @returns User
   */
  async createNormalUser(
    accountId: number,
    externalUserId: string,
    role: string,
    auto_renew: boolean,
    license_alert: boolean,
    notification: boolean,
    author_id: string,
    accepted_terms_version: string,
  ): Promise<User> {
    const user = new User();

    user.role = role;
    user.account_id = accountId;
    user.external_id = externalUserId;
    user.auto_renew = auto_renew;
    user.license_alert = license_alert;
    user.notification = notification;
    user.author_id = author_id;
    user.accepted_terms_version = accepted_terms_version;

    const createdEntity = await this.dataSource.transaction(
      async (entityManager) => {
        const repo = entityManager.getRepository(User);
        const newUser = repo.create(user);
        const persisted = await repo.save(newUser);

        // ユーザーのタスクソート条件を作成
        const sortCriteria = new SortCriteria();
        {
          sortCriteria.parameter = getTaskListSortableAttribute('JOB_NUMBER');
          sortCriteria.direction = getDirection('ASC');
          sortCriteria.user_id = persisted.id;
        }
        const sortCriteriaRepo = entityManager.getRepository(SortCriteria);
        const newSortCriteria = sortCriteriaRepo.create(sortCriteria);
        await sortCriteriaRepo.save(newSortCriteria);

        return persisted;
      },
    );
    return createdEntity;
  }

  async findVerifiedUser(sub: string): Promise<User | undefined> {
    const user = await this.dataSource.getRepository(User).findOne({
      where: {
        external_id: sub,
        email_verified: true,
      },
    });

    if (!user) {
      return undefined;
    }
    return user;
  }

  async findUserByExternalId(sub: string): Promise<User> {
    const user = await this.dataSource.getRepository(User).findOne({
      where: {
        external_id: sub,
      },
      relations: {
        account: true,
      },
    });

    if (!user) {
      throw new UserNotFoundError();
    }
    return user;
  }

  async findUserById(id: number): Promise<User | undefined> {
    const user = await this.dataSource.getRepository(User).findOne({
      where: {
        id: id,
      },
    });

    if (!user) {
      return undefined;
    }
    return user;
  }

  /**
   * AuthorIdが既に存在するか確認する
   * @param user
   * @returns 存在する：true 存在しない：false
   */
  async existsAuthorId(
    accountId: number,
    authorId: string,
  ): Promise<boolean | undefined> {
    const user = await this.dataSource.getRepository(User).findOne({
      where: [
        {
          account_id: accountId,
          author_id: authorId,
        },
      ],
    });

    if (user) {
      return true;
    }
    return false;
  }

  /**
   * 特定の情報でユーザーを更新する
   * @param user
   * @returns update
   */
  async update(user: User): Promise<UpdateResult> {
    return await this.dataSource.transaction(async (entityManager) => {
      const repo = entityManager.getRepository(User);
      return await repo.update({ id: user.id }, user);
    });
  }

  /**
   * 管理ユーザーがメール認証済みなら認証情報を更新する
   * @param user
   * @returns update
   */
  async updateUserVerified(id: number): Promise<UpdateResult> {
    return await this.dataSource.transaction(async (entityManager) => {
      const repo = entityManager.getRepository(User);
      const targetUser = await repo.findOne({
        where: {
          id: id,
        },
      });

      // 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
      if (!targetUser) {
        throw new UserNotFoundError();
      }

      if (targetUser.email_verified) {
        throw new EmailAlreadyVerifiedError();
      }

      targetUser.email_verified = true;

      return await repo.update({ id: targetUser.id }, targetUser);
    });
  }

  /**
   * 同じアカウントIDを持つユーザーを探す
   * @param externalId
   * @returns User[]
   */
  async findSameAccountUsers(external_id: string): Promise<User[]> {
    return await this.dataSource.transaction(async (entityManager) => {
      const repo = entityManager.getRepository(User);

      const accountId = (await repo.findOne({ where: { external_id } }))
        .account_id;

      const dbUsers = await this.dataSource.getRepository(User).find({
        relations: {
          userGroupMembers: {
            userGroup: true,
          },
          license: true,
        },
        where: { account_id: accountId },
      });

      return dbUsers;
    });
  }

  /**
   * アカウント内のタイピストユーザーを取得する
   * @param sub
   * @returns typist users
   */
  async findTypistUsers(sub: string): Promise<User[]> {
    return await this.dataSource.transaction(async (entityManager) => {
      const repo = entityManager.getRepository(User);

      const user = await repo.findOne({
        where: {
          external_id: sub,
        },
      });

      const typists = await repo.find({
        where: {
          account_id: user.account_id,
          role: USER_ROLES.TYPIST,
          deleted_at: IsNull(),
        },
      });

      return typists;
    });
  }
}
