import { NewAllocatedLicenseExpirationDate } from './types/types';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { HttpException, HttpStatus } from '@nestjs/common';
import { LicensesService } from './licenses.service';
import { makeTestingModule } from '../../common/test/modules';
import { DataSource } from 'typeorm';
import {
  createCardLicense,
  createLicense,
  createCardLicenseIssue,
  createLicenseAllocationHistory,
  selectCardLicensesCount,
  selectCardLicense,
  selectLicense,
  selectLicenseAllocationHistory,
  createOrder,
  selectOrderLicense,
} from './test/utility';
import { UsersService } from '../users/users.service';
import { Context, makeContext } from '../../common/log';
import { ADB2C_SIGN_IN_TYPE, LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../constants';
import {
  makeHierarchicalAccounts,
  makeTestSimpleAccount,
  makeTestUser,
} from '../../common/test/utility';
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
import { overrideAdB2cService, overrideSendgridService } from '../../common/test/overrides';
import { truncateAllTable } from '../../common/test/init';

describe('ライセンス注文', () => {
  let source: DataSource | null = null;
  beforeAll(async () => {
    if (source == null) {
      source = await (async () => {
        const s = new DataSource({
          type: 'mysql',
          host: 'test_mysql_db',
          port: 3306,
          username: 'user',
          password: 'password',
          database: 'odms',
          entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
          synchronize: false, // trueにすると自動的にmigrationが行われるため注意
        });
        return await s.initialize();
      })();
    }
  });

  beforeEach(async () => {
    if (source) {
      await truncateAllTable(source);
    }
  });

  afterAll(async () => {
    await source?.destroy();
    source = null;
  });

  it('ライセンス注文が完了する', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId, parent_account_id: parentAccountId } =
      await makeTestSimpleAccount(source, {
        parent_account_id: 2,
      });
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
    });

    const poNumber = 'test1';
    const orderCount = 100;

    const service = module.get<LicensesService>(LicensesService);

    const context = makeContext(`uuidv4`, 'requestId');
    await service.licenseOrders(context, externalId, poNumber, orderCount);
    const dbSelectResult = await selectOrderLicense(
      source,
      accountId,
      poNumber,
    );

    expect(dbSelectResult.orderLicense?.po_number).toEqual(poNumber);
    expect(dbSelectResult.orderLicense?.quantity).toEqual(orderCount);
    expect(dbSelectResult.orderLicense?.from_account_id).toEqual(accountId);
    expect(dbSelectResult.orderLicense?.to_account_id).toEqual(parentAccountId);
    expect(dbSelectResult.orderLicense?.status).toEqual('Issue Requesting');
  });

  it('POナンバー重複時、エラーとなる', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source, {
      parent_account_id: 2,
    });
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
    });

    const poNumber = 'test1';
    const orderCount = 100;

    const service = module.get<LicensesService>(LicensesService);

    const context = makeContext(`uuidv4`, 'requestId');
    try {
      await service.licenseOrders(context, externalId, poNumber, orderCount);
      await service.licenseOrders(context, externalId, poNumber, orderCount);
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010401'));
      } else {
        fail();
      }
    }
  });

  it('ユーザID取得できなかった場合、エラーとなる', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source, {
      parent_account_id: 2,
    });
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'useId',
      role: 'admin',
    });

    const poNumber = 'test1';
    const orderCount = 100;

    // 存在しないのユーザーIDを作成
    const notExistUserId = 'notExistId';

    const service = module.get<LicensesService>(LicensesService);

    const context = makeContext(`uuidv4`, 'requestId');

    try {
      await service.licenseOrders(
        context,
        notExistUserId,
        poNumber,
        orderCount,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
      } else {
        fail();
      }
    }
  });

  it('親ユーザID取得できなかった場合、エラーとなる', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source, {
      parent_account_id: undefined,
    });
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
    });

    const poNumber = 'test1';
    const orderCount = 100;

    const service = module.get<LicensesService>(LicensesService);

    const context = makeContext(`uuidv4`, 'requestId');
    try {
      await service.licenseOrders(context, externalId, poNumber, orderCount);
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010501'));
      } else {
        fail();
      }
    }
  });
});

describe('カードライセンス発行', () => {
  let source: DataSource | null = null;
  beforeAll(async () => {
    if (source == null) {
      source = await (async () => {
        const s = new DataSource({
          type: 'mysql',
          host: 'test_mysql_db',
          port: 3306,
          username: 'user',
          password: 'password',
          database: 'odms',
          entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
          synchronize: false, // trueにすると自動的にmigrationが行われるため注意
        });
        return await s.initialize();
      })();
    }
  });

  beforeEach(async () => {
    if (source) {
      await truncateAllTable(source);
    }
  });

  afterAll(async () => {
    await source?.destroy();
    source = null;
  });

  it('カードライセンス発行が完了する(発行数が合っているか確認)', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });

    const service = module.get<LicensesService>(LicensesService);
    const issueCount = 500;
    const context = makeContext(`uuidv4`, 'requestId');
    await service.issueCardLicenseKeys(context, externalId, issueCount);
    const dbSelectResult = await selectCardLicensesCount(source);
    expect(dbSelectResult.count).toEqual(issueCount);
  });

  it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });

    const service = module.get<LicensesService>(LicensesService);
    const issueCount = 500;
    const context = makeContext(`uuidv4`, 'requestId');

    //DBアクセスに失敗するようにする
    const licensesService = module.get<LicensesRepositoryService>(
      LicensesRepositoryService,
    );
    licensesService.createCardLicenses = jest
      .fn()
      .mockRejectedValue('DB failed');

    try {
      await service.issueCardLicenseKeys(context, externalId, issueCount);
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }
  });
});

describe('カードライセンスを取り込む', () => {
  let source: DataSource | null = null;
  beforeAll(async () => {
    if (source == null) {
      source = await (async () => {
        const s = new DataSource({
          type: 'mysql',
          host: 'test_mysql_db',
          port: 3306,
          username: 'user',
          password: 'password',
          database: 'odms',
          entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
          synchronize: false, // trueにすると自動的にmigrationが行われるため注意
        });
        return await s.initialize();
      })();
    }
  });

  beforeEach(async () => {
    if (source) {
      await truncateAllTable(source);
    }
  });

  afterAll(async () => {
    await source?.destroy();
    source = null;
  });
  it('カードライセンス取り込みが完了する', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });

    const cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
    const defaultAccountId = 150;
    const license_id = 50;
    const issueId = 100;

    await createLicense(
      source,
      license_id,
      null,
      defaultAccountId,
      LICENSE_TYPE.CARD,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    await createCardLicense(source, license_id, issueId, cardLicenseKey);
    await createCardLicenseIssue(source, issueId);

    const service = module.get<LicensesService>(LicensesService);
    const context = makeContext(`uuidv4`, 'requestId');

    await service.activateCardLicenseKey(context, externalId, cardLicenseKey);
    const dbSelectResultFromCardLicense = await selectCardLicense(
      source,
      cardLicenseKey,
    );
    const dbSelectResultFromLicense = await selectLicense(source, license_id);
    expect(
      dbSelectResultFromCardLicense?.cardLicense?.activated_at,
    ).toBeDefined();
    expect(dbSelectResultFromLicense?.license?.account_id).toEqual(accountId);
  });

  it('取込可能なライセンスのみが取得できる', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const now = new Date();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });

    // ライセンスを作成する
    // 1件目
    await createLicense(
      source,
      1,
      new Date(now.getTime() + 60 * 60 * 1000),
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    // 2件目、expiry_dateがnull(OneYear)
    await createLicense(
      source,
      2,
      null,
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    // 3件目、1件目と同じ有効期限
    await createLicense(
      source,
      3,
      new Date(now.getTime() + 60 * 60 * 1000),
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    // 4件目、expiry_dateが一番遠いデータ
    await createLicense(
      source,
      4,
      new Date(now.getTime() + 60 * 60 * 1000 * 2),
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    // 5件目、expiry_dateがnull(OneYear)
    await createLicense(
      source,
      5,
      null,
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    // 6件目、ライセンス状態が割当済
    await createLicense(
      source,
      6,
      new Date(now.getTime() + 60 * 60 * 1000 * 2),
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.ALLOCATED,
      null,
      null,
      null,
      null,
    );
    // 7件目、ライセンス状態が削除済
    await createLicense(
      source,
      7,
      new Date(now.getTime() + 60 * 60 * 1000 * 2),
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.DELETED,
      null,
      null,
      null,
      null,
    );
    // 8件目、別アカウントの未割当のライセンス
    await createLicense(
      source,
      8,
      new Date(now.getTime() + 60 * 60 * 1000),
      accountId + 1,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    // 9件目、有効期限切れのライセンス
    await createLicense(
      source,
      9,
      new Date(now.getTime() - 60 * 60 * 1000 * 24),
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    const service = module.get<LicensesService>(LicensesService);
    const context = makeContext('userId', 'requestId');
    const response = await service.getAllocatableLicenses(context, externalId);
    // 対象外のデータは取得していないことを確認する
    expect(response.allocatableLicenses.length).toBe(5);
    // ソートして取得されていることを確認する
    // （expiry_dateがnullを最優先、次に有効期限が遠い順(同じ有効期限の場合はID昇順)
    expect(response.allocatableLicenses[0].licenseId).toBe(2);
    expect(response.allocatableLicenses[1].licenseId).toBe(5);
    expect(response.allocatableLicenses[2].licenseId).toBe(4);
    expect(response.allocatableLicenses[3].licenseId).toBe(1);
    expect(response.allocatableLicenses[4].licenseId).toBe(3);
  });

  it('カードライセンス取り込みに失敗した場合、エラーになる（ライセンスが存在しないエラー）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });

    //存在しないライセンスキーを作成
    const notExistCardLicenseKey = 'XXCETXC0Z9PQZ9GKRGGY';

    const service = module.get<LicensesService>(LicensesService);
    const context = makeContext(`uuidv4`, 'requestId');

    try {
      await service.activateCardLicenseKey(
        context,
        externalId,
        notExistCardLicenseKey,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010801'));
      } else {
        fail();
      }
    }
  });

  it('カードライセンス取り込みに失敗した場合、エラーになる（ライセンスが既に取り込まれているエラー）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });

    const cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
    const defaultAccountId = 150;
    const license_id = 50;
    const issueId = 100;

    await createLicense(
      source,
      license_id,
      null,
      defaultAccountId,
      LICENSE_TYPE.CARD,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    await createCardLicense(source, license_id, issueId, cardLicenseKey);
    await createCardLicenseIssue(source, issueId);

    const service = module.get<LicensesService>(LicensesService);
    const context = makeContext(`uuidv4`, 'requestId');

    try {
      await service.activateCardLicenseKey(context, externalId, cardLicenseKey);
      await service.activateCardLicenseKey(context, externalId, cardLicenseKey);
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010802'));
      } else {
        fail();
      }
    }
  });

  it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });

    const service = module.get<LicensesService>(LicensesService);
    const context = makeContext(`uuidv4`, 'requestId');
    const cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';

    //DBアクセスに失敗するようにする
    const licensesService = module.get<LicensesRepositoryService>(
      LicensesRepositoryService,
    );
    licensesService.activateCardLicense = jest
      .fn()
      .mockRejectedValue('DB failed');
    try {
      await service.activateCardLicenseKey(context, externalId, cardLicenseKey);
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }
  });
});

describe('ライセンス割り当て', () => {
  let source: DataSource | null = null;
  beforeAll(async () => {
    if (source == null) {
      source = await (async () => {
        const s = new DataSource({
          type: 'mysql',
          host: 'test_mysql_db',
          port: 3306,
          username: 'user',
          password: 'password',
          database: 'odms',
          entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
          synchronize: false, // trueにすると自動的にmigrationが行われるため注意
        });
        return await s.initialize();
      })();
    }
  });

  beforeEach(async () => {
    if (source) {
      await truncateAllTable(source);
    }
  });

  afterAll(async () => {
    await source?.destroy();
    source = null;
  });

  it('未割当のライセンスに対して、ライセンス割り当てが完了する', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: dealerId } = await makeTestSimpleAccount(source, { company_name: "DEALER_COMPANY", tier: 4 });
    const { id: dealerAdminId } = await makeTestUser(source, {
      account_id: dealerId,
      external_id: 'userId_admin',
      role: 'admin',
      author_id: undefined,
    });

    const { id: accountId } = await makeTestSimpleAccount(source, {
      parent_account_id: dealerId,
      tier: 5
    });
    const { id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });
    await createLicense(
      source,
      1,
      null,
      accountId,
      LICENSE_TYPE.CARD,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    await createLicenseAllocationHistory(
      source,
      1,
      userId,
      1,
      accountId,
      'NONE',
    );

    const service = module.get<UsersService>(UsersService);
    let _subject: string = '';
    let _url: string | undefined = '';
    overrideAdB2cService(service, {
      getUser: async (context, externalId) => {
        return {
          displayName: 'TEMP' + externalId,
          id: externalId,
          identities: [
            {
              signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
              issuer: 'xxxxxx',
              issuerAssignedId: 'mail@example.com',
            },
          ],
        };
      },
      getUsers: async (context, externalIds) => {
        return externalIds.map((x) => ({
          displayName: 'admin',
          id: x,
          identities: [
            {
              signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
              issuer: 'xxxxxx',
              issuerAssignedId: `mail+${x}@example.com`,
            },
          ],
        }));
      }
    });

    overrideSendgridService(service, {
      sendMail: async (
        context: Context,
        to: string[],
        cc: string[],
        from: string,
        subject: string,
        text: string,
        html: string,
      ) => {
        const urlPattern = /https?:\/\/[^\s]+/g;
        const urls = text.match(urlPattern);
        const url = urls?.pop();

        _subject = subject;
        _url = url;
      },
    });

    const expiry_date = new NewAllocatedLicenseExpirationDate();

    await service.allocateLicense(
      makeContext('trackingId', 'requestId'),
      userId,
      1,
    );
    const resultLicense = await selectLicense(source, 1);
    expect(resultLicense.license?.allocated_user_id).toBe(userId);
    expect(resultLicense.license?.status).toBe(
      LICENSE_ALLOCATED_STATUS.ALLOCATED,
    );
    expect(resultLicense.license?.expiry_date?.setMilliseconds(0)).toEqual(
      expiry_date.setMilliseconds(0),
    );
    const licenseAllocationHistory = await selectLicenseAllocationHistory(
      source,
      userId,
      1,
    );
    expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
      userId,
    );
    expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
      1,
    );
    expect(
      licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
    ).toBe(true);
    expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
      accountId,
    );

    expect(_subject).toBe('License Assigned Notification [U-108]');
    expect(_url).toBe('http://localhost:8081/');
  });

  it('再割り当て可能なライセンスに対して、ライセンス割り当てが完了する', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });
    const date = new Date();
    date.setDate(date.getDate() + 30);
    date.setMilliseconds(0);
    await createLicense(
      source,
      1,
      date,
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.REUSABLE,
      null,
      null,
      null,
      null,
    );
    await createLicenseAllocationHistory(
      source,
      1,
      userId,
      1,
      accountId,
      'NONE',
    );

    const service = module.get<UsersService>(UsersService);
    overrideSendgridService(service, {});

    await service.allocateLicense(
      makeContext('trackingId', 'requestId'),
      userId,
      1,
    );
    const result = await selectLicense(source, 1);
    expect(result.license?.allocated_user_id).toBe(userId);
    expect(result.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
    expect(result.license?.expiry_date).toEqual(date);
    const licenseAllocationHistory = await selectLicenseAllocationHistory(
      source,
      userId,
      1,
    );
    expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
      userId,
    );
    expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
      1,
    );
    expect(
      licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
    ).toBe(true);
    expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
      accountId,
    );
  });

  it('未割当のライセンスに対して、別のライセンスが割り当てられているユーザーの割り当てが完了する', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });
    const date = new Date();
    date.setDate(date.getDate() + 30);
    date.setMilliseconds(0);

    await createLicense(
      source,
      1,
      date,
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.ALLOCATED,
      userId,
      null,
      null,
      null,
    );
    await createLicense(
      source,
      2,
      null,
      accountId,
      LICENSE_TYPE.CARD,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    await createLicenseAllocationHistory(
      source,
      1,
      userId,
      1,
      accountId,
      'NONE',
    );

    const service = module.get<UsersService>(UsersService);
    overrideSendgridService(service, {});
    const expiry_date = new NewAllocatedLicenseExpirationDate();

    await service.allocateLicense(
      makeContext('trackingId', 'requestId'),
      userId,
      2,
    );

    // もともと割り当てられていたライセンスの状態確認
    const result1 = await selectLicense(source, 1);
    expect(result1.license?.allocated_user_id).toBe(null);
    expect(result1.license?.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE);
    expect(result1.license?.expiry_date).toEqual(date);
    const licenseAllocationHistory = await selectLicenseAllocationHistory(
      source,
      userId,
      1,
    );
    expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
      userId,
    );
    expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
      1,
    );
    expect(
      licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
    ).toBe(false);
    expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
      accountId,
    );

    // 新たに割り当てたライセンスの状態確認
    const result2 = await selectLicense(source, 2);
    expect(result2.license?.allocated_user_id).toBe(userId);
    expect(result2.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
    expect(result2.license?.expiry_date?.setMilliseconds(0)).toEqual(
      expiry_date.setMilliseconds(0),
    );
    const newlicenseAllocationHistory = await selectLicenseAllocationHistory(
      source,
      userId,
      2,
    );
    expect(newlicenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
      userId,
    );
    expect(
      newlicenseAllocationHistory.licenseAllocationHistory?.license_id,
    ).toBe(2);
    expect(
      newlicenseAllocationHistory.licenseAllocationHistory?.is_allocated,
    ).toBe(true);
    expect(
      newlicenseAllocationHistory.licenseAllocationHistory?.account_id,
    ).toBe(accountId);
  });

  it('割り当て時にライセンス履歴テーブルへの登録が完了する（元がNORMALのとき）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });
    const date = new Date();
    date.setDate(date.getDate() + 30);
    date.setMilliseconds(0);
    await createLicense(
      source,
      1,
      date,
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.ALLOCATED,
      userId,
      null,
      null,
      null,
    );
    await createLicense(
      source,
      2,
      null,
      accountId,
      LICENSE_TYPE.CARD,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    await createLicenseAllocationHistory(
      source,
      1,
      userId,
      1,
      accountId,
      'NONE',
    );

    const service = module.get<UsersService>(UsersService);
    overrideSendgridService(service, {});
    await service.allocateLicense(
      makeContext('trackingId', 'requestId'),
      userId,
      2,
    );

    const licenseAllocationHistory = await selectLicenseAllocationHistory(
      source,
      userId,
      2,
    );
    expect(
      licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
    ).toBe('NONE');
  });

  it('割り当て時にライセンス履歴テーブルへの登録が完了する（元がCARDのとき）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });
    const date = new Date();
    date.setDate(date.getDate() + 30);
    date.setMilliseconds(0);
    await createLicense(
      source,
      1,
      date,
      accountId,
      LICENSE_TYPE.CARD,
      LICENSE_ALLOCATED_STATUS.ALLOCATED,
      userId,
      null,
      null,
      null,
    );
    await createLicense(
      source,
      2,
      null,
      accountId,
      LICENSE_TYPE.CARD,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    await createLicenseAllocationHistory(
      source,
      1,
      userId,
      1,
      accountId,
      'CARD',
    );

    const service = module.get<UsersService>(UsersService);
    overrideSendgridService(service, {});
    await service.allocateLicense(
      makeContext('trackingId', 'requestId'),
      userId,
      2,
    );

    const licenseAllocationHistory = await selectLicenseAllocationHistory(
      source,
      userId,
      2,
    );
    expect(
      licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
    ).toBe('CARD');
  });

  it('割り当て時にライセンス履歴テーブルへの登録が完了する（元がTRIALのとき）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });
    const date = new Date();
    date.setDate(date.getDate() + 30);
    date.setMilliseconds(0);
    await createLicense(
      source,
      1,
      date,
      accountId,
      LICENSE_TYPE.TRIAL,
      LICENSE_ALLOCATED_STATUS.ALLOCATED,
      userId,
      null,
      null,
      null,
    );
    await createLicense(
      source,
      2,
      null,
      accountId,
      LICENSE_TYPE.CARD,
      LICENSE_ALLOCATED_STATUS.UNALLOCATED,
      null,
      null,
      null,
      null,
    );
    await createLicenseAllocationHistory(
      source,
      1,
      userId,
      1,
      accountId,
      'TRIAL',
    );

    const service = module.get<UsersService>(UsersService);
    overrideSendgridService(service, {});
    await service.allocateLicense(
      makeContext('trackingId', 'requestId'),
      userId,
      2,
    );

    const licenseAllocationHistory = await selectLicenseAllocationHistory(
      source,
      userId,
      2,
    );
    expect(
      licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
    ).toBe('TRIAL');
  });

  it('有効期限が切れているライセンスを割り当てようとした場合、エラーになる', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });
    const date = new Date();
    date.setDate(date.getDate() - 30);
    date.setMilliseconds(0);
    await createLicense(
      source,
      1,
      date,
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.REUSABLE,
      null,
      null,
      null,
      null,
    );

    const service = module.get<UsersService>(UsersService);
    overrideSendgridService(service, {});

    await expect(
      service.allocateLicense(
        makeContext('trackingId', 'requestId'),
        userId,
        1,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
    );
  });

  it('割り当て不可なライセンスを割り当てようとした場合、エラーになる', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });
    const date = new Date();
    date.setDate(date.getDate() + 30);
    date.setMilliseconds(0);
    await createLicense(
      source,
      1,
      null,
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.ALLOCATED,
      null,
      null,
      null,
      null,
    );
    await createLicense(
      source,
      2,
      null,
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.DELETED,
      null,
      null,
      null,
      null,
    );

    const service = module.get<UsersService>(UsersService);
    overrideSendgridService(service, {});

    await expect(
      service.allocateLicense(
        makeContext('trackingId', 'requestId'),
        userId,
        1,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010806'), HttpStatus.BAD_REQUEST),
    );
    await expect(
      service.allocateLicense(
        makeContext('trackingId', 'requestId'),
        userId,
        2,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010806'), HttpStatus.BAD_REQUEST),
    );
  });
});

describe('ライセンス割り当て解除', () => {
  let source: DataSource | null = null;
  beforeAll(async () => {
    if (source == null) {
      source = await (async () => {
        const s = new DataSource({
          type: 'mysql',
          host: 'test_mysql_db',
          port: 3306,
          username: 'user',
          password: 'password',
          database: 'odms',
          entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
          synchronize: false, // trueにすると自動的にmigrationが行われるため注意
        });
        return await s.initialize();
      })();
    }
  });

  beforeEach(async () => {
    if (source) {
      await truncateAllTable(source);
    }
  });

  afterAll(async () => {
    await source?.destroy();
    source = null;
  });

  it('ライセンスの割り当て解除が完了する', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });
    const date = new Date();
    date.setDate(date.getDate() + 30);
    date.setMilliseconds(0);
    await createLicense(
      source,
      1,
      date,
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.ALLOCATED,
      userId,
      null,
      null,
      null,
    );
    await createLicenseAllocationHistory(
      source,
      1,
      userId,
      1,
      accountId,
      'NONE',
    );

    const service = module.get<UsersService>(UsersService);
    overrideSendgridService(service, {});
    await service.deallocateLicense(
      makeContext('trackingId', 'requestId'),
      userId,
    );

    // 割り当て解除したライセンスの状態確認
    const deallocatedLicense = await selectLicense(source, 1);
    expect(deallocatedLicense.license?.allocated_user_id).toBe(null);
    expect(deallocatedLicense.license?.status).toBe(
      LICENSE_ALLOCATED_STATUS.REUSABLE,
    );
    expect(deallocatedLicense.license?.expiry_date).toEqual(date);

    // ライセンス履歴テーブルの状態確認
    const licenseAllocationHistory = await selectLicenseAllocationHistory(
      source,
      userId,
      1,
    );
    expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
      userId,
    );
    expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
      1,
    );
    expect(
      licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
    ).toBe(false);
    expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
      accountId,
    );
    expect(
      licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
    ).toBe('NONE');
  });

  it('ライセンスが既に割り当て解除されていた場合、エラーとなる', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { id: accountId } = await makeTestSimpleAccount(source);
    const { id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId',
      role: 'admin',
      author_id: undefined,
    });
    await makeTestUser(source, {
      account_id: accountId,
      external_id: 'userId2',
      role: 'admin',
      author_id: undefined,
    });
    const date = new Date();
    date.setDate(date.getDate() + 30);
    date.setMilliseconds(0);
    await createLicense(
      source,
      1,
      date,
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.ALLOCATED,
      2,
      null,
      null,
      null,
    );
    await createLicense(
      source,
      2,
      date,
      accountId,
      LICENSE_TYPE.NORMAL,
      LICENSE_ALLOCATED_STATUS.REUSABLE,
      userId,
      null,
      null,
      null,
    );
    await createLicenseAllocationHistory(
      source,
      1,
      userId,
      1,
      accountId,
      'NONE',
    );

    const service = module.get<UsersService>(UsersService);
    overrideSendgridService(service, {});
    await expect(
      service.deallocateLicense(makeContext('trackingId', 'requestId'), userId),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010807'), HttpStatus.BAD_REQUEST),
    );
  });
});

describe('ライセンス注文キャンセル', () => {
  let source: DataSource | null = null;
  beforeAll(async () => {
    if (source == null) {
      source = await (async () => {
        const s = new DataSource({
          type: 'mysql',
          host: 'test_mysql_db',
          port: 3306,
          username: 'user',
          password: 'password',
          database: 'odms',
          entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
          synchronize: false, // trueにすると自動的にmigrationが行われるため注意
        });
        return await s.initialize();
      })();
    }
  });

  beforeEach(async () => {
    if (source) {
      await truncateAllTable(source);
    }
  });

  afterAll(async () => {
    await source?.destroy();
    source = null;
  });

  it('ライセンス注文のキャンセルが完了する', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
      source,
    );
    const poNumber = 'CANCEL_TEST';
    await createOrder(
      source,
      poNumber,
      tier2Accounts[0].account.id,
      tier2Accounts[0].account.parent_account_id ?? 0,
      null,
      10,
      'Issue Requesting',
    );
    // キャンセル済みの同名poNumoberが存在しても正常に動作することの確認用order
    await createOrder(
      source,
      poNumber,
      tier2Accounts[0].account.id,
      tier2Accounts[0].account.parent_account_id ?? 0,
      null,
      10,
      'Order Canceled',
    );

    const service = module.get<LicensesService>(LicensesService);
    overrideSendgridService(service, {});
    await service.cancelOrder(
      makeContext('trackingId', 'requestId'),
      tier2Accounts[0].users[0].external_id,
      poNumber,
    );

    // 割り当て解除したライセンスの状態確認
    const orderRecord = await selectOrderLicense(
      source,
      tier2Accounts[0].account.id,
      poNumber,
    );
    expect(orderRecord.orderLicense?.canceled_at).toBeDefined();
    expect(orderRecord.orderLicense?.status).toBe('Order Canceled');
  });

  it('ライセンスが既に発行済みの場合、エラーとなる', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
      source,
    );
    const poNumber = 'CANCEL_TEST';
    await createOrder(
      source,
      poNumber,
      tier2Accounts[0].account.id,
      tier2Accounts[0].account.parent_account_id ?? 0,
      null,
      10,
      'Issued',
    );

    const service = module.get<LicensesService>(LicensesService);
    overrideSendgridService(service, {});
    await expect(
      service.cancelOrder(
        makeContext('trackingId', 'requestId'),
        tier2Accounts[0].users[0].external_id,
        poNumber,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010808'), HttpStatus.BAD_REQUEST),
    );
  });

  it('ライセンスが既にキャンセル済みの場合、エラーとなる', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();

    const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
      source,
    );
    const poNumber = 'CANCEL_TEST';
    await createOrder(
      source,
      poNumber,
      tier2Accounts[0].account.id,
      tier2Accounts[0].account.parent_account_id ?? 0,
      null,
      10,
      'Order Canceled',
    );

    const service = module.get<LicensesService>(LicensesService);
    overrideSendgridService(service, {});
    await expect(
      service.cancelOrder(
        makeContext('trackingId', 'requestId'),
        tier2Accounts[0].users[0].external_id,
        poNumber,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010808'), HttpStatus.BAD_REQUEST),
    );
  });
});
