import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { sign } from '../../common/jwt';
import sendgrid from '@sendgrid/mail';
import { getPrivateKey } from '../../common/jwt/jwt';
import { Context } from '../../common/log';
import { readFileSync } from 'node:fs';
import path from 'node:path';
import {
  PRIMARY_ADMIN_NAME,
  AUTHOR_NAME,
  CUSTOMER_NAME,
  DEALER_NAME,
  FILE_NAME,
  LICENSE_QUANTITY,
  PO_NUMBER,
  TOP_URL,
  USER_EMAIL,
  USER_NAME,
  TYPIST_NAME,
  VERIFY_LINK,
  TEMPORARY_PASSWORD,
} from '../../templates/constants';
import { URL } from 'node:url';

@Injectable()
export class SendGridService {
  private readonly logger = new Logger(SendGridService.name);
  private readonly emailConfirmLifetime: number;
  private readonly appDomain: string;
  private readonly mailFrom: string;
  private readonly templateU101Html: string;
  private readonly templateU101Text: string;
  private readonly templateU102Html: string;
  private readonly templateU102Text: string;
  private readonly templateU105Html: string;
  private readonly templateU105Text: string;
  private readonly templateU106Html: string;
  private readonly templateU106Text: string;
  private readonly templateU107Html: string;
  private readonly templateU107Text: string;
  private readonly templateU108Html: string;
  private readonly templateU108Text: string;
  private readonly templateU109Html: string;
  private readonly templateU109Text: string;
  private readonly templateU111Html: string;
  private readonly templateU111Text: string;
  private readonly templateU112Html: string;
  private readonly templateU112Text: string;
  // U-112のテンプレート差分（親アカウントがない場合）
  private readonly templateU112NoParentHtml: string;
  private readonly templateU112NoParentText: string;
  private readonly templateU113Html: string;
  private readonly templateU113Text: string;
  private readonly templateU114Html: string;
  private readonly templateU114Text: string;
  private readonly templateU115Html: string;
  private readonly templateU115Text: string;
  private readonly templateU117Html: string;
  private readonly templateU117Text: string;

  constructor(private readonly configService: ConfigService) {
    this.appDomain = this.configService.getOrThrow<string>('APP_DOMAIN');
    this.mailFrom = this.configService.getOrThrow<string>('MAIL_FROM');
    this.emailConfirmLifetime = this.configService.getOrThrow<number>(
      'EMAIL_CONFIRM_LIFETIME',
    );
    const key = this.configService.getOrThrow<string>('SENDGRID_API_KEY');
    sendgrid.setApiKey(key);

    // メールテンプレートを読み込む
    {
      this.templateU101Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_101.html`),
        'utf-8',
      );
      this.templateU101Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_101.txt`),
        'utf-8',
      );
      this.templateU102Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_102.html`),
        'utf-8',
      );
      this.templateU102Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_102.txt`),
        'utf-8',
      );
      this.templateU105Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_105.html`),
        'utf-8',
      );
      this.templateU105Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_105.txt`),
        'utf-8',
      );
      this.templateU106Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_106.html`),
        'utf-8',
      );
      this.templateU106Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_106.txt`),
        'utf-8',
      );
      this.templateU107Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_107.html`),
        'utf-8',
      );
      this.templateU107Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_107.txt`),
        'utf-8',
      );
      this.templateU108Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_108.html`),
        'utf-8',
      );
      this.templateU108Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_108.txt`),
        'utf-8',
      );
      this.templateU109Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_109.html`),
        'utf-8',
      );
      this.templateU109Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_109.txt`),
        'utf-8',
      );
      this.templateU111Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_111.html`),
        'utf-8',
      );
      this.templateU111Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_111.txt`),
        'utf-8',
      );
      this.templateU112Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_112.html`),
        'utf-8',
      );
      this.templateU112Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_112.txt`),
        'utf-8',
      );
      this.templateU112NoParentHtml = readFileSync(
        path.resolve(
          __dirname,
          `../../templates/template_U_112_no_parent.html`,
        ),
        'utf-8',
      );
      this.templateU112NoParentText = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_112_no_parent.txt`),
        'utf-8',
      );
      this.templateU113Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_113.html`),
        'utf-8',
      );
      this.templateU113Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_113.txt`),
        'utf-8',
      );
      this.templateU114Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_114.html`),
        'utf-8',
      );
      this.templateU114Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_114.txt`),
        'utf-8',
      );
      this.templateU115Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_115.html`),
        'utf-8',
      );
      this.templateU115Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_115.txt`),
        'utf-8',
      );
      this.templateU117Html = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_117.html`),
        'utf-8',
      );
      this.templateU117Text = readFileSync(
        path.resolve(__dirname, `../../templates/template_U_117.txt`),
        'utf-8',
      );
    }
  }

  /**
   * U-101のテンプレートを使用したメールを送信する
   * @param context
   * @param customerMail アカウント登録を行った管理者ユーザーのメールアドレス
   * @param customerAccountName アカウント登録した会社名
   * @returns mail with u101
   */
  async sendMailWithU101(
    context: Context,
    customerMail: string,
    customerAccountName: string,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU101.name}`,
    );
    try {
      const subject = 'Account Registered Notification [U-101]';
      const url = new URL(this.appDomain).href;
      const html = this.templateU101Html
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(TOP_URL, url);
      const text = this.templateU101Text
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(TOP_URL, url);

      await this.sendMail(
        context,
        [customerMail],
        [],
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU101.name}`,
      );
    }
  }

  /**
   * U-102のテンプレートを使用したメールを送信する
   * @param context
   * @param adminEmail 登録したアカウントの管理者(primary)のメールアドレス
   * @param accountId 登録したアカウントのID
   * @param userId 登録したユーザーのID
   * @returns mail with u102
   */
  async sendMailWithU102(
    context: Context,
    adminEmail: string,
    accountId: number,
    userId: number,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU102.name}`,
    );
    try {
      const privateKey = getPrivateKey(this.configService);
      const token = sign<{ accountId: number; userId: number; email: string }>(
        {
          accountId,
          userId,
          email: adminEmail,
        },
        this.emailConfirmLifetime,
        privateKey,
      );
      const paths = path.join('mail-confirm');
      const url = new URL(paths, this.appDomain).href;
      const verifyUrl = `${url}?verify=${token}`;

      const subject = 'User Registration Notification [U-102]';
      const html = this.templateU102Html.replaceAll(VERIFY_LINK, verifyUrl);
      const text = this.templateU102Text.replaceAll(VERIFY_LINK, verifyUrl);

      await this.sendMail(
        context,
        [adminEmail],
        [],
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU102.name}`,
      );
    }
  }

  /**
   * U-105のテンプレートを使用したメールを送信する
   * @param context
   * @param customerMails 注文を行ったアカウントの管理者(primary/secondary)のメールアドレス
   * @param customerAccountName 送信対象の企業名
   * @param lisenceCount 注文を行った対象の注文の内容（ライセンス数）
   * @param poNumber 注文を行った対象の注文の内容（PO番号）
   * @param dealerEmails 問題発生時に問い合わせする先の上位のディーラーの管理者(primary/secondary)のメールアドレス
   * @param dealerAccountName 問題発生時に問い合わせする先の上位のディーラー名（会社名）
   * @returns mail with u105
   */
  async sendMailWithU105(
    context: Context,
    customerMails: string[],
    customerAccountName: string,
    lisenceCount: number,
    poNumber: string,
    dealerEmails: string[],
    dealerAccountName: string,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU105.name}`,
    );
    try {
      const subject = 'License Requested Notification [U-105]';

      // メールの本文を作成する
      const html = this.templateU105Html
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(DEALER_NAME, dealerAccountName)
        .replaceAll(PO_NUMBER, poNumber)
        .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
      const text = this.templateU105Text
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(DEALER_NAME, dealerAccountName)
        .replaceAll(PO_NUMBER, poNumber)
        .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);

      // メールを送信する
      await this.sendMail(
        context,
        customerMails,
        dealerEmails,
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU105.name}`,
      );
    }
  }

  /**
   * U-106のテンプレートを使用したメールを送信する
   * @param context
   * @param cancelUserEmailAddress 注文キャンセルを行ったアカウントの管理者(primary/secondary)のメールアドレス
   * @param customerAccountName 送信対象の企業名
   * @param lisenceCount 注文キャンセルを行った対象の注文の内容（ライセンス数）
   * @param poNumber 注文キャンセルを行った対象の注文の内容（PO番号）
   * @param dealerEmails 問題発生時に問い合わせする先の上位のディーラーの管理者(primary/secondary)のメールアドレス
   * @param dealerAccountName 問題発生時に問い合わせする先の上位のディーラー名（会社名）
   * @returns mail with u106
   */
  async sendMailWithU106(
    context: Context,
    customerMails: string[],
    customerAccountName: string,
    lisenceCount: number,
    poNumber: string,
    dealerEmails: string[],
    dealerAccountName: string,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU106.name}`,
    );
    try {
      const subject = 'Cancelled License Order Notification [U-106]';

      // メールの本文を作成する
      const html = this.templateU106Html
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(DEALER_NAME, dealerAccountName)
        .replaceAll(PO_NUMBER, poNumber)
        .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
      const text = this.templateU106Text
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(DEALER_NAME, dealerAccountName)
        .replaceAll(PO_NUMBER, poNumber)
        .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);

      // メールを送信する
      await this.sendMail(
        context,
        customerMails,
        dealerEmails,
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU106.name}`,
      );
    }
  }

  /**
   * U-107のテンプレートを使用したメールを送信する
   * @param context
   * @param cancelUserEmailAddress ライセンス発行をされたアカウントの管理者(primary/secondary)のメールアドレス
   * @param customerAccountName 送信対象の企業名
   * @param lisenceCount ライセンス発行を行った対象の注文の内容（ライセンス数）
   * @param poNumber ライセンス発行を行った対象の注文の内容（PO番号）
   * @param dealerEmails 問題発生時に問い合わせする先の上位のディーラーの管理者(primary/secondary)のメールアドレス
   * @param dealerAccountName 問題発生時に問い合わせする先の上位のディーラー名（会社名）
   * @returns mail with u107
   */
  async sendMailWithU107(
    context: Context,
    customerMails: string[],
    customerAccountName: string,
    lisenceCount: number,
    poNumber: string,
    dealerEmails: string[],
    dealerAccountName: string,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU107.name}`,
    );
    try {
      const subject = 'License Issued Notification [U-107]';

      // メールの本文を作成する
      const html = this.templateU107Html
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(DEALER_NAME, dealerAccountName)
        .replaceAll(PO_NUMBER, poNumber)
        .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
      const text = this.templateU107Text
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(DEALER_NAME, dealerAccountName)
        .replaceAll(PO_NUMBER, poNumber)
        .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);

      // メールを送信する
      await this.sendMail(
        context,
        customerMails,
        dealerEmails,
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU107.name}`,
      );
    }
  }

  /**
   * U-108のテンプレートを使用したメールを送信する
   * @param context
   * @param userName ライセンス割り当てされたユーザーの名前
   * @param userMail ライセンス割り当てされたユーザーのメールアドレス
   * @param customerAdminMails ライセンス割り当てされたユーザーの所属するアカウントの管理者(primary/secondary)のメールアドレス
   * @param customerAccountName ライセンス割り当てされたユーザーの所属するアカウントの名前
   * @param dealerAccountName 問題発生時に問い合わせする先の上位のディーラー名（会社名）
   * @returns mail with u108
   */
  async sendMailWithU108(
    context: Context,
    userName: string,
    userMail: string,
    customerAdminMails: string[],
    customerAccountName: string,
    dealerAccountName: string,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU108.name}`,
    );
    try {
      const subject = 'License Assigned Notification [U-108]';
      const url = new URL(this.appDomain).href;

      // メールの本文を作成する
      const html = this.templateU108Html
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(DEALER_NAME, dealerAccountName)
        .replaceAll(USER_NAME, userName)
        .replaceAll(USER_EMAIL, userMail)
        .replaceAll(TOP_URL, url);
      const text = this.templateU108Text
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(DEALER_NAME, dealerAccountName)
        .replaceAll(USER_NAME, userName)
        .replaceAll(USER_EMAIL, userMail)
        .replaceAll(TOP_URL, url);

      const ccAddress = customerAdminMails.includes(userMail) ? [] : [userMail];

      // メールを送信する
      await this.sendMail(
        context,
        customerAdminMails,
        ccAddress,
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU108.name}`,
      );
    }
  }

  /**
   * U-109のテンプレートを使用したメールを送信する
   * @param context context
   * @param dealerEmails ライセンス発行をキャンセルした上位アカウントの管理者(primary/secondary)のメールアドレス
   * @param dealerAccountName ライセンス発行をキャンセルした上位アカウントの会社名
   * @param lisenceCount ライセンス発行をキャンセルした対象の注文の内容（ライセンス数）
   * @param poNumber ライセンス発行をキャンセルした対象の注文の内容（PO番号）
   * @param customerMails ライセンス発行をキャンセルされたアカウントの管理者(primary/secondary)のメールアドレス
   * @param customerAccountName ライセンス発行をキャンセルされたアカウントの会社名
   * @returns
   */
  async sendMailWithU109(
    context: Context,
    dealerEmails: string[],
    dealerAccountName: string,
    lisenceCount: number,
    poNumber: string,
    customerMails: string[],
    customerAccountName: string,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU109.name}`,
    );
    try {
      const subject = 'License Returned Notification [U-109]';

      // メールの本文を作成する
      const html = this.templateU109Html
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(DEALER_NAME, dealerAccountName)
        .replaceAll(PO_NUMBER, poNumber)
        .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
      const text = this.templateU109Text
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(DEALER_NAME, dealerAccountName)
        .replaceAll(PO_NUMBER, poNumber)
        .replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);

      // メールを送信する
      await this.sendMail(
        context,
        dealerEmails,
        customerMails,
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU109.name}`,
      );
    }
  }

  /**
   * U-111のテンプレートを使用したメールを送信する
   * @param context
   * @param primaryAdminName 削除されたアカウントの管理者(primary)の名前
   * @param primaryAdminMail 削除されたアカウントの管理者(primary)のメールアドレス
   * @param customerAccountName 削除されたアカウントの会社名
   * @returns mail with u111
   */
  async sendMailWithU111(
    context: Context,
    primaryAdminName: string,
    primaryAdminMail: string,
    customerAccountName: string,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU111.name}`,
    );
    try {
      const subject = 'Account Deleted Notification [U-111]';
      const url = new URL(this.appDomain).href;

      // メールの本文を作成する
      const html = this.templateU111Html
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
        .replaceAll(TOP_URL, url);

      const text = this.templateU111Text
        .replaceAll(CUSTOMER_NAME, customerAccountName)
        .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
        .replaceAll(TOP_URL, url);

      // メールを送信する
      await this.sendMail(
        context,
        [primaryAdminMail],
        [],
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU111.name}`,
      );
    }
  }

  /**
   * U-112のテンプレートを使用したメールを送信する
   * @param context
   * @param primaryAdminName 情報変更を行ったアカウントの管理者(primary)の名前
   * @param primaryAdminMail 情報変更を行ったアカウントの管理者(primary)のメールアドレス
   * @param customerAccountName 情報変更を行ったアカウントの名前
   * @param dealerAccountName 問題発生時に問い合わせする先の上位のディーラー名（会社名）
   * @returns mail with u112
   */
  async sendMailWithU112(
    context: Context,
    primaryAdminName: string,
    primaryAdminMail: string,
    customerAccountName: string,
    dealerAccountName: string | null,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU112.name}`,
    );
    try {
      const subject = 'Account Edit Notification [U-112]';

      let html: string;
      let text: string;
      const url = new URL(this.appDomain).href;

      // 親アカウントがない場合は別のテンプレートを使用する
      if (dealerAccountName === null) {
        // メールの本文を作成する
        html = this.templateU112NoParentHtml
          .replaceAll(CUSTOMER_NAME, customerAccountName)
          .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
          .replaceAll(TOP_URL, url);
        text = this.templateU112NoParentText
          .replaceAll(CUSTOMER_NAME, customerAccountName)
          .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
          .replaceAll(TOP_URL, url);
      } else {
        html = this.templateU112Html
          .replaceAll(CUSTOMER_NAME, customerAccountName)
          .replaceAll(DEALER_NAME, dealerAccountName)
          .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
          .replaceAll(TOP_URL, url);
        text = this.templateU112Text
          .replaceAll(CUSTOMER_NAME, customerAccountName)
          .replaceAll(DEALER_NAME, dealerAccountName)
          .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
          .replaceAll(TOP_URL, url);
      }

      // メールを送信する
      await this.sendMail(
        context,
        [primaryAdminMail],
        [],
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU112.name}`,
      );
    }
  }

  /**
   * U-113のテンプレートを使用したメールを送信する
   * @param context
   * @param userMail 認証済みユーザーのメールアドレス
   * @param primaryAdminName 認証済みユーザーが所属するアカウントの管理者(primary)の名前
   * @param temporaryPassword 認証済みユーザーの一時パスワード
   * @returns mail with u113
   */
  async sendMailWithU113(
    context: Context,
    userMail: string,
    primaryAdminName: string,
    temporaryPassword: string,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU113.name}`,
    );
    try {
      const subject = 'Temporary password [U-113]';

      // メールの本文を作成する
      const html = this.templateU113Html
        .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
        .replaceAll(TEMPORARY_PASSWORD, temporaryPassword);
      const text = this.templateU113Text
        .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
        .replaceAll(TEMPORARY_PASSWORD, temporaryPassword);

      // メールを送信する
      await this.sendMail(
        context,
        [userMail],
        [],
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU113.name}`,
      );
    }
  }

  /**
   * U-114のテンプレートを使用したメールを送信する
   * @param context
   * @param accountId 認証対象のユーザーが所属するアカウントのID
   * @param userId 認証対象のユーザーのID
   * @param userMail 認証対象のユーザーのメールアドレス
   * @param primaryAdminName 認証対象のユーザーが所属するアカウントの管理者(primary)の名前
   * @returns mail with u114
   */
  async sendMailWithU114(
    context: Context,
    accountId: number,
    userId: number,
    userMail: string,
    primaryAdminName: string,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU114.name}`,
    );
    try {
      // ユーザー認証用のトークンを作成する
      const privateKey = getPrivateKey(this.configService);
      const token = sign<{ accountId: number; userId: number; email: string }>(
        {
          accountId,
          userId,
          email: userMail,
        },
        this.emailConfirmLifetime,
        privateKey,
      );

      const paths = path.join('mail-confirm', '/user');
      const url = new URL(paths, this.appDomain);
      const verifyUrl = `${url}?verify=${token}`;

      const subject = 'User Registration Notification [U-114]';

      // メールの本文を作成する
      const html = this.templateU114Html
        .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
        .replaceAll(VERIFY_LINK, verifyUrl);
      const text = this.templateU114Text
        .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
        .replaceAll(VERIFY_LINK, verifyUrl);

      // メールを送信する
      await this.sendMail(
        context,
        [userMail],
        [],
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU114.name}`,
      );
    }
  }

  /**
   * U-115のテンプレートを使用したメールを送信する
   * @param context
   * @param userName 情報更新が行われたユーザーの名前
   * @param userMail 情報更新が行われたユーザーのメールアドレス
   * @param primaryAdminName 情報更新が行われたユーザーの所属するアカウントの管理者(primary)の名前
   * @param adminMails 情報更新が行われたユーザーの所属するアカウントの管理者(primary/secondary)のメールアドレス
   * @returns mail with u115
   */
  async sendMailWithU115(
    context: Context,
    userName: string,
    userMail: string,
    primaryAdminName: string,
    adminMails: string[],
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU115.name}`,
    );
    try {
      const subject = 'Edit User Notification [U-115]';

      // メールの本文を作成する
      const html = this.templateU115Html
        .replaceAll(USER_NAME, userName)
        .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName);
      const text = this.templateU115Text
        .replaceAll(USER_NAME, userName)
        .replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName);

      // 管理者ユーザーの情報を変更した場合にはTOに管理者のメールアドレスを設定するので、CCには管理者のメールアドレスを設定しない
      const ccAdminMails = adminMails.filter((x) => x !== userMail);

      // メールを送信する
      await this.sendMail(
        context,
        [userMail],
        ccAdminMails,
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU115.name}`,
      );
    }
  }

  /**
   * U-117のテンプレートを使用したメールを送信する
   * @param context
   * @param authorEmail 文字起こしファイルのAuthorのメールアドレス
   * @param typistEmail 文字起こしを行ったTypistのメールアドレス
   * @param authorName 文字起こしファイルのAuthorの名前
   * @param fileName 文字起こしファイルのファイル名
   * @param typistName 文字起こしを行ったTypistの名前
   * @param adminName アカウント管理者の名前（プライマリ）
   * @returns mail with u117
   */
  async sendMailWithU117(
    context: Context,
    authorEmail: string,
    typistEmail: string,
    authorName: string,
    fileName: string,
    typistName: string,
    adminName: string,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${this.sendMailWithU117.name}`,
    );
    try {
      const subject = 'Transcription Completion Notification [U-117]';

      // メールの本文を作成する
      const html = this.templateU117Html
        .replaceAll(AUTHOR_NAME, authorName)
        .replaceAll(FILE_NAME, fileName)
        .replaceAll(TYPIST_NAME, typistName)
        .replaceAll(PRIMARY_ADMIN_NAME, adminName);
      const text = this.templateU117Text
        .replaceAll(AUTHOR_NAME, authorName)
        .replaceAll(FILE_NAME, fileName)
        .replaceAll(TYPIST_NAME, typistName)
        .replaceAll(PRIMARY_ADMIN_NAME, adminName);

      // メールを送信する
      await this.sendMail(
        context,
        [authorEmail, typistEmail],
        [],
        this.mailFrom,
        subject,
        text,
        html,
      );
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMailWithU117.name}`,
      );
    }
  }

  /**
   * メールを送信する
   * @param context
   * @param to
   * @param cc
   * @param from
   * @param subject
   * @param text
   * @param html
   * @returns mail
   */
  async sendMail(
    context: Context,
    to: string[],
    cc: string[],
    from: string,
    subject: string,
    text: string,
    html: string,
  ): Promise<void> {
    this.logger.log(`[IN] [${context.getTrackingId()}] ${this.sendMail.name}`);
    try {
      const res = await sendgrid
        .send({
          from: {
            email: from,
          },
          to: to.map((v) => ({ email: v })),
          cc: cc.map((v) => ({ email: v })),
          subject: subject,
          text: text,
          html: html,
        })
        .then((v) => v[0]);
      this.logger.log(
        `[${context.getTrackingId()}] status code: ${
          res.statusCode
        } body: ${JSON.stringify(res.body)}`,
      );
    } catch (e) {
      this.logger.error(`[${context.getTrackingId()}] error=${e}`);
      throw e;
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.sendMail.name}`,
      );
    }
  }
}
