import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
  NotificationHubsClient,
  createAppleNotificationBody,
  createAppleNotification,
  createTagExpression,
  createWindowsInstallation,
  createAppleInstallation,
  createWindowsRawNotification,
} from '@azure/notification-hubs';
import { TAG_MAX_COUNT } from '../../constants';
import { PNS } from '../../constants';
import { Context } from '../../common/log';
import { NotificationBody } from '../../common/notify/types/types';

@Injectable()
export class NotificationhubService {
  private readonly logger = new Logger(NotificationhubService.name);
  private readonly client: NotificationHubsClient;
  constructor(private readonly configService: ConfigService) {
    this.client = new NotificationHubsClient(
      this.configService.getOrThrow<string>('NOTIFICATION_HUB_CONNECT_STRING'),
      this.configService.getOrThrow<string>('NOTIFICATION_HUB_NAME'),
    );
  }

  /**
   * Registers notificationhub service
   * @param pns
   * @param pnsHandler
   * @returns register
   */
  async register(
    context: Context,
    userId: number,
    pns: string,
    pnsHandler: string,
    installationId: string,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${
        this.register.name
      } | params: { userId: ${userId}, pns: ${pns}, pnsHandler: ${pnsHandler}, installationId: ${installationId} }`,
    );

    const tag = `user_${userId}`;

    //登録情報作成
    const installation = {
      // XXX 登録の有効期限を設定したい場合
      //expirationTime: '',
      installationId: installationId,
      pushChannel: pnsHandler,
      tags: [tag],
    };

    try {
      switch (pns) {
        case PNS.WNS:
          await this.client.createOrUpdateInstallation(
            createWindowsInstallation(installation),
          );
          break;
        case PNS.APNS:
          await this.client.createOrUpdateInstallation(
            createAppleInstallation(installation),
          );
          break;
        default:
          throw new Error('invalid pns');
      }
    } catch (e) {
      this.logger.error(`[${context.getTrackingId()}] error=${e.message}`);
      throw e;
    } finally {
      this.logger.log(
        `[OUT] [${context.getTrackingId()}] ${this.register.name}`,
      );
    }
  }

  /**
   * 指定したタグのユーザーに通知を送信する
   * @param context
   * @param tags
   * @param bodyContent
   * @returns notify
   */
  async notify(
    context: Context,
    tags: string[],
    bodyContent: NotificationBody,
  ): Promise<void> {
    this.logger.log(
      `[IN] [${context.getTrackingId()}] ${
        this.notify.name
      } | params: { tags: ${tags}, bodyContent: ${JSON.stringify(
        bodyContent,
      )} }`,
    );

    try {
      // OR条件によるtag指定は20個までなので分割して送信する
      const chunkTags = splitArrayInChunks(tags, TAG_MAX_COUNT);

      for (let index = 0; index < chunkTags.length; index++) {
        const currentTags = chunkTags[index];
        const tagExpression = createTagExpression(currentTags);

        // Windows
        try {
          const body = {
            wns: {
              alert: '',
            },
            newDictation: bodyContent,
          };
          const notification = createWindowsRawNotification({
            body: JSON.stringify(body),
          });
          const result = await this.client.sendNotification(notification, {
            tagExpression,
          });
          this.logger.log(`[${context.getTrackingId()}] ${result}`);
        } catch (e) {
          this.logger.error(`[${context.getTrackingId()}] error=${e}`);
        }
        // Apple
        try {
          const body = createAppleNotificationBody({
            aps: {
              alert: '',
            },
            newDictation: bodyContent,
          });
          const notification = createAppleNotification({ body });
          const result = await this.client.sendNotification(notification, {
            tagExpression,
          });
          this.logger.log(`[${context.getTrackingId()}] ${result}`);
        } catch (e) {
          this.logger.error(`[${context.getTrackingId()}] error=${e}`);
        }
      }
    } catch (e) {
      throw e;
    } finally {
      this.logger.log(`[OUT] [${context.getTrackingId()}] ${this.notify.name}`);
    }
  }
}

const splitArrayInChunks = (arr: string[], size: number): string[][] => {
  const result: string[][] = [];
  for (let i = 0; i < arr.length; i += size) {
    result.push(arr.slice(i, i + size));
  }
  return result;
};
