import { Injectable, Logger } from '@nestjs/common';
import {
  BlobServiceClient,
  StorageSharedKeyCredential,
  ContainerClient,
  ContainerSASPermissions,
  BlobSASPermissions,
  generateBlobSASQueryParameters,
  BlobClient,
} from '@azure/storage-blob';
import { ConfigService } from '@nestjs/config';
import {
  BLOB_STORAGE_REGION_AU,
  BLOB_STORAGE_REGION_EU,
  BLOB_STORAGE_REGION_US,
} from '../../constants';
import { Context } from '../../common/log';

@Injectable()
export class BlobstorageService {
  private readonly logger = new Logger(BlobstorageService.name);
  private readonly blobServiceClientUS: BlobServiceClient;
  private readonly blobServiceClientEU: BlobServiceClient;
  private readonly blobServiceClientAU: BlobServiceClient;
  private readonly sharedKeyCredentialUS: StorageSharedKeyCredential;
  private readonly sharedKeyCredentialAU: StorageSharedKeyCredential;
  private readonly sharedKeyCredentialEU: StorageSharedKeyCredential;
  constructor(private readonly configService: ConfigService) {
    this.sharedKeyCredentialUS = new StorageSharedKeyCredential(
      this.configService.get('STORAGE_ACCOUNT_NAME_US'),
      this.configService.get('STORAGE_ACCOUNT_KEY_US'),
    );
    this.sharedKeyCredentialAU = new StorageSharedKeyCredential(
      this.configService.get('STORAGE_ACCOUNT_NAME_AU'),
      this.configService.get('STORAGE_ACCOUNT_KEY_AU'),
    );
    this.sharedKeyCredentialEU = new StorageSharedKeyCredential(
      this.configService.get('STORAGE_ACCOUNT_NAME_EU'),
      this.configService.get('STORAGE_ACCOUNT_KEY_EU'),
    );
    this.blobServiceClientUS = new BlobServiceClient(
      this.configService.get('STORAGE_ACCOUNT_ENDPOINT_US'),
      this.sharedKeyCredentialUS,
    );
    this.blobServiceClientAU = new BlobServiceClient(
      this.configService.get('STORAGE_ACCOUNT_ENDPOINT_AU'),
      this.sharedKeyCredentialAU,
    );
    this.blobServiceClientEU = new BlobServiceClient(
      this.configService.get('STORAGE_ACCOUNT_ENDPOINT_EU'),
      this.sharedKeyCredentialEU,
    );
  }
  /**
   * Creates container
   * @param companyName
   * @returns container
   */
  async createContainer(accountId: number, country: string): Promise<void> {
    this.logger.log(`[IN] ${this.createContainer.name}`);

    // 国に応じたリージョンでコンテナ名を指定してClientを取得
    const containerClient = this.getContainerClient(accountId, country);

    try {
      // コンテナ作成
      await containerClient.create();
    } catch (e) {
      this.logger.error(`error=${e}`);
      throw e;
    } finally {
      this.logger.log(`[OUT] ${this.createContainer.name}`);
    }
  }
  /**
   * Containers exists
   * @param country
   * @param accountId
   * @returns exists
   */
  async containerExists(
    context: Context,
    accountId: number,
    country: string,
  ): Promise<boolean> {
    this.logger.log(
      `[IN] [${context.trackingId}] ${this.containerExists.name}`,
    );
    // 国に応じたリージョンでコンテナ名を指定してClientを取得
    const containerClient = this.getContainerClient(accountId, country);
    const exists = await containerClient.exists();

    this.logger.log(
      `[OUT] [${context.trackingId}] ${this.containerExists.name}`,
    );
    return exists;
  }

  /**
   * Files exists
   * @param accountId
   * @param country
   * @param path
   * @param fileName
   * @param containerUrl
   * @returns exists
   */
  async fileExists(
    context: Context,
    accountId: number,
    country: string,
    filePath: string,
  ): Promise<boolean> {
    this.logger.log(`[IN] [${context.trackingId}] ${this.fileExists.name}`);

    const containerClient = this.getContainerClient(accountId, country);
    const blob = containerClient.getBlobClient(`${filePath}`);
    const exists = await blob.exists();

    this.logger.log(`[OUT] [${context.trackingId}] ${this.fileExists.name}`);
    return exists;
  }

  /**
   * SASトークン付きのBlobStorageアップロードURLを生成し返却します
   * @param accountId
   * @param userId
   * @param country
   * @returns upload sas
   */
  async publishUploadSas(
    context: Context,
    accountId: number,
    userId: number,
    country: string,
  ): Promise<string> {
    this.logger.log(
      `[IN] [${context.trackingId}] ${this.publishUploadSas.name}`,
    );
    let containerClient: ContainerClient;
    let sharedKeyCredential: StorageSharedKeyCredential;
    try {
      // コンテナ名を指定してClientを取得
      containerClient = this.getContainerClient(accountId, country);
      // 国に対応したリージョンの接続情報を取得する
      sharedKeyCredential = this.getSharedKeyCredential(country);
    } catch (e) {
      this.logger.error(`error=${e}`);
      this.logger.log(
        `[OUT] [${context.trackingId}] ${this.publishUploadSas.name}`,
      );
      throw e;
    }

    //SASの有効期限を設定
    const expiryDate = new Date();
    expiryDate.setHours(
      expiryDate.getHours() +
        this.configService.get('STORAGE_TOKEN_EXPIRE_TIME'),
    );

    //SASの権限を設定。Pendingにしたものを再アップロードする運用をするため、上書き可能にする
    const permissions = new ContainerSASPermissions();
    permissions.write = true;

    //SASを発行
    const sasToken = generateBlobSASQueryParameters(
      {
        containerName: containerClient.containerName,
        permissions: permissions,
        startsOn: new Date(),
        expiresOn: expiryDate,
      },
      sharedKeyCredential,
    );

    const url = new URL(containerClient.url);
    url.pathname += `/${userId}`;
    url.search = `${sasToken}`;

    this.logger.log(
      `[OUT] [${context.trackingId}] ${this.publishUploadSas.name}`,
    );
    return url.toString();
  }

  /**
   * SASトークン付きのBlobStorageダウンロードURLを生成し返却します
   * @param accountId
   * @param country
   * @param path
   * @param fileName
   * @returns download sas
   */
  async publishDownloadSas(
    context: Context,
    accountId: number,
    country: string,
    filePath: string,
  ): Promise<string> {
    this.logger.log(
      `[IN] [${context.trackingId}] ${this.publishDownloadSas.name}`,
    );
    let containerClient: ContainerClient;
    let blobClient: BlobClient;
    let sharedKeyCredential: StorageSharedKeyCredential;
    try {
      // コンテナ名を指定してClientを取得
      containerClient = this.getContainerClient(accountId, country);
      // コンテナ内のBlobパス名を指定してClientを取得
      blobClient = containerClient.getBlobClient(`${filePath}`);
      // 国に対応したリージョンの接続情報を取得する
      sharedKeyCredential = this.getSharedKeyCredential(country);
    } catch (e) {
      this.logger.error(`error=${e}`);
      this.logger.log(
        `[OUT] [${context.trackingId}] ${this.publishDownloadSas.name}`,
      );
      throw e;
    }

    //SASの有効期限を設定
    const expiryDate = new Date();
    expiryDate.setHours(
      expiryDate.getHours() +
        this.configService.get('STORAGE_TOKEN_EXPIRE_TIME'),
    );

    //SASの権限を設定（ダウンロードのため読み取り許可）
    const permissions = new BlobSASPermissions();
    permissions.read = true;

    //SASを発行
    const sasToken = generateBlobSASQueryParameters(
      {
        containerName: containerClient.containerName,
        blobName: blobClient.name,
        permissions: permissions,
        startsOn: new Date(),
        expiresOn: expiryDate,
      },
      sharedKeyCredential,
    );

    const url = new URL(blobClient.url);
    url.search = `${sasToken}`;

    this.logger.log(
      `[OUT] [${context.trackingId}] ${this.publishDownloadSas.name}`,
    );
    return url.toString();
  }

  /**
   * Gets container client
   * @param companyName
   * @returns container client
   */
  private getContainerClient(
    accountId: number,
    country: string,
  ): ContainerClient {
    const containerName = `account-${accountId}`;
    if (BLOB_STORAGE_REGION_US.includes(country)) {
      return this.blobServiceClientUS.getContainerClient(containerName);
    } else if (BLOB_STORAGE_REGION_AU.includes(country)) {
      return this.blobServiceClientAU.getContainerClient(containerName);
    } else if (BLOB_STORAGE_REGION_EU.includes(country)) {
      return this.blobServiceClientEU.getContainerClient(containerName);
    } else {
      throw new Error('invalid country');
    }
  }

  /**
   * Gets shared key credential
   * @param country
   * @returns shared key credential
   */
  private getSharedKeyCredential(country: string): StorageSharedKeyCredential {
    if (BLOB_STORAGE_REGION_US.includes(country)) {
      return this.sharedKeyCredentialUS;
    } else if (BLOB_STORAGE_REGION_AU.includes(country)) {
      return this.sharedKeyCredentialAU;
    } else if (BLOB_STORAGE_REGION_EU.includes(country)) {
      return this.sharedKeyCredentialEU;
    } else {
      throw new Error('invalid country');
    }
  }
}
