import { HttpException, HttpStatus } from '@nestjs/common';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import {
  makeBlobstorageServiceMockValue,
  makeDefaultTasksRepositoryMockValue,
  makeDefaultUsersRepositoryMockValue,
  makeFilesServiceMock,
} from './test/files.service.mock';
import { DataSource } from 'typeorm';
import {
  createAccount,
  createTask,
  createUser,
  makeTestingModuleWithBlob,
} from './test/utility';
import { FilesService } from './files.service';
import { makeContext } from '../../common/log';

describe('音声ファイルアップロードURL取得', () => {
  it('アップロードSASトークンが乗っているURLを返却する', async () => {
    const blobParam = makeBlobstorageServiceMockValue();
    const userRepoParam = makeDefaultUsersRepositoryMockValue();
    const taskRepoParam = makeDefaultTasksRepositoryMockValue();
    const service = await makeFilesServiceMock(
      blobParam,
      userRepoParam,
      taskRepoParam,
    );

    expect(
      await service.publishUploadSas(makeContext('trackingId'), {
        userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
        role: 'Author',
        tier: 5,
      }),
    ).toEqual('https://blob-storage?sas-token');
  });

  it('アカウント専用コンテナが無い場合でも、コンテナ作成しURLを返却する', async () => {
    const blobParam = makeBlobstorageServiceMockValue();
    const userRepoParam = makeDefaultUsersRepositoryMockValue();
    const taskRepoParam = makeDefaultTasksRepositoryMockValue();

    blobParam.containerExists = false;

    const service = await makeFilesServiceMock(
      blobParam,
      userRepoParam,
      taskRepoParam,
    );

    expect(
      await service.publishUploadSas(makeContext('trackingId'), {
        userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
        role: 'Author',
        tier: 5,
      }),
    ).toEqual('https://blob-storage?sas-token');
  });

  it('ユーザー情報の取得に失敗した場合、例外エラーを返却する', async () => {
    const blobParam = makeBlobstorageServiceMockValue();
    const taskRepoParam = makeDefaultTasksRepositoryMockValue();

    const service = await makeFilesServiceMock(
      blobParam,
      {
        findUserByExternalId: new Error(''),
      },
      taskRepoParam,
    );

    await expect(
      service.publishUploadSas(makeContext('trackingId'), {
        userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
        role: 'Author',
        tier: 5,
      }),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
    );
  });

  it('コンテナ作成に失敗した場合、例外エラーを返却する', async () => {
    const blobParam = makeBlobstorageServiceMockValue();
    const taskRepoParam = makeDefaultTasksRepositoryMockValue();

    const service = await makeFilesServiceMock(
      blobParam,
      {
        findUserByExternalId: new Error(''),
      },
      taskRepoParam,
    );
    blobParam.publishUploadSas = new Error('Azure service down');

    await expect(
      service.publishUploadSas(makeContext('trackingId'), {
        userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
        role: 'Author',
        tier: 5,
      }),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
    );
  });
});

describe('タスク作成', () => {
  it('文字起こしタスクを作成できる', async () => {
    const blobParam = makeBlobstorageServiceMockValue();
    const userRepoParam = makeDefaultUsersRepositoryMockValue();
    const taskRepoParam = makeDefaultTasksRepositoryMockValue();
    const service = await makeFilesServiceMock(
      blobParam,
      userRepoParam,
      taskRepoParam,
    );

    expect(
      await service.uploadFinished(
        makeContext('trackingId'),
        'userId',
        'http://blob/url/file.zip',
        'AUTHOR_01',
        'file.zip',
        '11:22:33',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        256,
        '01',
        'DS2',
        'comment',
        'workTypeID',
        optionItemList,
        false,
      ),
    ).toEqual({ jobNumber: '00000001' });
  });

  it('日付フォーマットが不正な場合、エラーを返却する', async () => {
    const blobParam = makeBlobstorageServiceMockValue();
    const userRepoParam = makeDefaultUsersRepositoryMockValue();
    const taskRepoParam = makeDefaultTasksRepositoryMockValue();
    const service = await makeFilesServiceMock(
      blobParam,
      userRepoParam,
      taskRepoParam,
    );

    await expect(
      service.uploadFinished(
        makeContext('trackingId'),
        'userId',
        'http://blob/url/file.zip',
        'AUTHOR_01',
        'file.zip',
        '11:22:33',
        'yyyy-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        256,
        '01',
        'DS2',
        'comment',
        'workTypeID',
        optionItemList,
        false,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
    );
  });

  it('オプションアイテムが10個ない場合、エラーを返却する', async () => {
    const blobParam = makeBlobstorageServiceMockValue();
    const userRepoParam = makeDefaultUsersRepositoryMockValue();
    const taskRepoParam = makeDefaultTasksRepositoryMockValue();
    const service = await makeFilesServiceMock(
      blobParam,
      userRepoParam,
      taskRepoParam,
    );

    await expect(
      service.uploadFinished(
        makeContext('trackingId'),
        'userId',
        'http://blob/url/file.zip',
        'AUTHOR_01',
        'file.zip',
        '11:22:33',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        256,
        '01',
        'DS2',
        'comment',
        'workTypeID',
        [
          {
            optionItemLabel: 'label_01',
            optionItemValue: 'value_01',
          },
        ],
        false,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
    );
  });

  it('タスク追加でユーザー情報の取得に失敗した場合、エラーを返却する', async () => {
    const blobParam = makeBlobstorageServiceMockValue();
    const taskRepoParam = makeDefaultTasksRepositoryMockValue();

    const service = await makeFilesServiceMock(
      blobParam,
      {
        findUserByExternalId: new Error(''),
      },
      taskRepoParam,
    );

    await expect(
      service.uploadFinished(
        makeContext('trackingId'),
        'userId',
        'http://blob/url/file.zip',
        'AUTHOR_01',
        'file.zip',
        '11:22:33',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        256,
        '01',
        'DS2',
        'comment',
        'workTypeID',
        optionItemList,
        false,
      ),
    ).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });

  it('タスクのDBへの追加に失敗した場合、エラーを返却する', async () => {
    const blobParam = makeBlobstorageServiceMockValue();
    const userRepoParam = makeDefaultUsersRepositoryMockValue();
    const service = await makeFilesServiceMock(blobParam, userRepoParam, {
      create: new Error(''),
      getTasksFromAccountId: new Error(),
    });

    await expect(
      service.uploadFinished(
        makeContext('trackingId'),
        'userId',
        'http://blob/url/file.zip',
        'AUTHOR_01',
        'file.zip',
        '11:22:33',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        256,
        '01',
        'DS2',
        'comment',
        'workTypeID',
        optionItemList,
        false,
      ),
    ).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });
});

describe('音声ファイルダウンロードURL取得', () => {
  let source: DataSource = null;
  beforeEach(async () => {
    source = new DataSource({
      type: 'sqlite',
      database: ':memory:',
      logging: false,
      entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
      synchronize: true, // trueにすると自動的にmigrationが行われるため注意
    });
    return source.initialize();
  });

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

  it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
    const { accountId } = await createAccount(source);
    const { externalId, userId, authorId } = await createUser(
      source,
      accountId,
      'author-user-external-id',
      'author',
      'AUTHOR_ID',
    );
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'InProgress',
      undefined,
      authorId,
    );

    const blobParam = makeBlobstorageServiceMockValue();
    blobParam.publishDownloadSas = `${url}?sas-token`;
    blobParam.fileExists = true;

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    expect(
      await service.publishAudioFileDownloadSas(
        makeContext('trackingId'),
        externalId,
        audioFileId,
      ),
    ).toEqual(`${url}?sas-token`);
  });

  it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
    const { accountId } = await createAccount(source);
    const { externalId, userId } = await createUser(
      source,
      accountId,
      'typist-user-external-id',
      'typist',
    );
    const { userId: authorUserId } = await createUser(
      source,
      accountId,
      'author-user-external-id',
      'author',
      'AUTHOR_ID',
    );

    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${authorUserId}`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'Finished',
      userId,
    );

    const blobParam = makeBlobstorageServiceMockValue();
    blobParam.publishDownloadSas = `${url}?sas-token`;
    blobParam.fileExists = true;

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.publishAudioFileDownloadSas(
        makeContext('trackingId'),
        externalId,
        audioFileId,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
    const { accountId } = await createAccount(source);
    const { externalId, userId } = await createUser(
      source,
      accountId,
      'typist-user-external-id',
      'typist',
    );
    const { userId: otherId } = await createUser(
      source,
      accountId,
      'other-typist-user-external-id',
      'typist',
    );
    const { userId: authorUserId } = await createUser(
      source,
      accountId,
      'author-user-external-id',
      'author',
      'AUTHOR_ID',
    );
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${authorUserId}`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'InProgress',
      otherId,
    );

    const blobParam = makeBlobstorageServiceMockValue();
    blobParam.publishDownloadSas = `${url}?sas-token`;
    blobParam.fileExists = true;

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.publishTemplateFileDownloadSas(
        makeContext('tracking'),
        externalId,
        audioFileId,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
    const { accountId } = await createAccount(source);
    const { externalId, userId } = await createUser(
      source,
      accountId,
      'author-user-external-id',
      'author',
      'AUTHOR_ID',
    );
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'InProgress',
      undefined,
      'OTHOR_ID',
    );

    const blobParam = makeBlobstorageServiceMockValue();
    blobParam.publishDownloadSas = `${url}?sas-token`;
    blobParam.fileExists = true;

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.publishAudioFileDownloadSas(
        makeContext('trackingId'),
        externalId,
        audioFileId,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('Taskが存在しない場合はエラーとなる', async () => {
    const { accountId } = await createAccount(source);
    const { externalId } = await createUser(
      source,
      accountId,
      'author-user-external-id',
      'author',
      'AUTHOR_ID',
    );

    const blobParam = makeBlobstorageServiceMockValue();

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.publishAudioFileDownloadSas(
        makeContext('trackingId'),
        externalId,
        1,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
    const { accountId } = await createAccount(source);
    const { externalId, userId, authorId } = await createUser(
      source,
      accountId,
      'author-user-external-id',
      'author',
      'AUTHOR_ID',
    );
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'InProgress',
      undefined,
      authorId,
    );

    const blobParam = makeBlobstorageServiceMockValue();
    blobParam.publishDownloadSas = `${url}?sas-token`;
    blobParam.fileExists = false;

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.publishAudioFileDownloadSas(
        makeContext('trackingId'),
        externalId,
        audioFileId,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
    );
  });
});

describe('テンプレートファイルダウンロードURL取得', () => {
  let source: DataSource = null;
  beforeEach(async () => {
    source = new DataSource({
      type: 'sqlite',
      database: ':memory:',
      logging: false,
      entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
      synchronize: true, // trueにすると自動的にmigrationが行われるため注意
    });
    return source.initialize();
  });

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

  it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
    const { accountId } = await createAccount(source);
    const { externalId, authorId } = await createUser(
      source,
      accountId,
      'author-user-external-id',
      'author',
      'AUTHOR_ID',
    );
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'InProgress',
      undefined,
      authorId,
    );

    const blobParam = makeBlobstorageServiceMockValue();
    blobParam.publishDownloadSas = `${url}?sas-token`;
    blobParam.fileExists = true;

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    expect(
      await service.publishTemplateFileDownloadSas(
        makeContext('tracking'),
        externalId,
        audioFileId,
      ),
    ).toEqual(`${url}?sas-token`);
  });

  it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
    const { accountId } = await createAccount(source);
    const { externalId, userId } = await createUser(
      source,
      accountId,
      'typist-user-external-id',
      'typist',
      undefined,
    );
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'Finished',
      userId,
    );

    const blobParam = makeBlobstorageServiceMockValue();
    blobParam.publishDownloadSas = `${url}?sas-token`;
    blobParam.fileExists = true;

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.publishTemplateFileDownloadSas(
        makeContext('tracking'),
        externalId,
        audioFileId,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
    const { accountId } = await createAccount(source);
    const { externalId } = await createUser(
      source,
      accountId,
      'typist-user-external-id',
      'typist',
      undefined,
    );
    const { userId: otherId } = await createUser(
      source,
      accountId,
      'other-typist-user-external-id',
      'typist',
      undefined,
    );
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'InProgress',
      otherId,
    );

    const blobParam = makeBlobstorageServiceMockValue();
    blobParam.publishDownloadSas = `${url}?sas-token`;
    blobParam.fileExists = true;

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.publishTemplateFileDownloadSas(
        makeContext('tracking'),
        externalId,
        audioFileId,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
    const { accountId } = await createAccount(source);
    const { externalId } = await createUser(
      source,
      accountId,
      'author-user-external-id',
      'author',
      'AUTHOR_ID',
    );
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'InProgress',
      undefined,
      'OTHOR_ID',
    );

    const blobParam = makeBlobstorageServiceMockValue();
    blobParam.publishDownloadSas = `${url}?sas-token`;
    blobParam.fileExists = true;

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.publishTemplateFileDownloadSas(
        makeContext('tracking'),
        externalId,
        audioFileId,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('Taskが存在しない場合はエラーとなる', async () => {
    const { accountId } = await createAccount(source);
    const { externalId } = await createUser(
      source,
      accountId,
      'author-user-external-id',
      'author',
      'AUTHOR_ID',
    );

    const blobParam = makeBlobstorageServiceMockValue();

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.publishTemplateFileDownloadSas(
        makeContext('tracking'),
        externalId,
        1,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
    const { accountId } = await createAccount(source);
    const { externalId, authorId } = await createUser(
      source,
      accountId,
      'author-user-external-id',
      'author',
      'AUTHOR_ID',
    );
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'InProgress',
      undefined,
      authorId,
    );

    const blobParam = makeBlobstorageServiceMockValue();
    blobParam.publishDownloadSas = `${url}?sas-token`;
    blobParam.fileExists = false;

    const module = await makeTestingModuleWithBlob(source, blobParam);
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.publishTemplateFileDownloadSas(
        makeContext('tracking'),
        externalId,
        audioFileId,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
    );
  });
});

const optionItemList = [
  {
    optionItemLabel: 'label_01',
    optionItemValue: 'value_01',
  },
  {
    optionItemLabel: 'label_02',
    optionItemValue: 'value_02',
  },
  {
    optionItemLabel: 'label_03',
    optionItemValue: 'value_03',
  },
  {
    optionItemLabel: 'label_04',
    optionItemValue: 'value_04',
  },
  {
    optionItemLabel: 'label_05',
    optionItemValue: 'value_05',
  },
  {
    optionItemLabel: 'label_06',
    optionItemValue: 'value_06',
  },
  {
    optionItemLabel: 'label_07',
    optionItemValue: 'value_07',
  },
  {
    optionItemLabel: 'label_08',
    optionItemValue: 'value_08',
  },
  {
    optionItemLabel: 'label_09',
    optionItemValue: 'value_09',
  },
  {
    optionItemLabel: 'label_10',
    optionItemValue: 'value_10',
  },
];
