import { Service, Inject } from 'typedi';
import { isUndefined, omit, keyBy } from 'lodash';
import {
  ISmsNotificationDefined,
  ISmsNotificationMeta,
  IEditSmsNotificationDTO,
  ISmsNotificationAllowedVariable,
} from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import SMSNotificationsConfig from 'config/smsNotifications';
import { ServiceError } from '@/exceptions';

const ERRORS = {
  SMS_NOTIFICATION_KEY_NOT_FOUND: 'SMS_NOTIFICATION_KEY_NOT_FOUND',
  UNSUPPORTED_SMS_MESSAGE_VARIABLES: 'UNSUPPORTED_SMS_MESSAGE_VARIABLES',
};

@Service()
export default class SmsNotificationsSettingsService {
  @Inject()
  private tenancy: TenancyService;

  /**
   * Retrieve sms notification meta from the given notification key.
   * @param {string} notificationKey - Notification key.
   * @returns {ISmsNotificationMeta}
   */
  public getSmsNotificationMeta = (
    tenantId: number,
    notificationKey: string
  ): ISmsNotificationMeta => {
    const notificationsByKey = keyBy(SMSNotificationsConfig, 'key');
    const notification = notificationsByKey[notificationKey];

    // Validates sms notification exists.
    this.validateSmsNotificationExists(notification);

    return this.transformSmsNotifConfigToMeta(tenantId, notification);
  };

  /**
   * Transformes the sms notification config to notificatin meta.
   * @param {Settings} settings
   * @param {ISmsNotificationDefined} smsNotification
   * @returns {ISmsNotificationMeta}
   */
  private transformSmsNotifConfigToMeta = (
    tenantId: number,
    smsNotification: ISmsNotificationDefined
  ): ISmsNotificationMeta => {
    const settings = this.tenancy.settings(tenantId);
    const i18n = this.tenancy.i18n(tenantId);
    const group = 'sms-notification';

    const defaultSmsMessage = i18n.__(smsNotification.defaultSmsMessage);

    return {
      ...omit(smsNotification, [
        'defaultSmsMessage',
        'defaultIsNotificationEnabled',
      ]),
      notificationLabel: i18n.__(smsNotification.notificationLabel),
      notificationDescription: i18n.__(smsNotification.notificationDescription),
      moduleFormatted: i18n.__(smsNotification.moduleFormatted),
      allowedVariables: smsNotification.allowedVariables.map(
        (notification) => ({
          ...notification,
          description: i18n.__(notification.description),
        })
      ),
      defaultSmsMessage,
      smsMessage: settings.get(
        {
          key: `sms-message.${smsNotification.key}`,
          group,
        },
        defaultSmsMessage
      ),
      isNotificationEnabled: settings.get(
        {
          key: `sms-notification-enable.${smsNotification.key}`,
          group,
        },
        smsNotification.defaultIsNotificationEnabled
      ),
    };
  };

  /**
   * Retrieve the sms notifications list.
   * @param {number} tenantId
   */
  public smsNotificationsList = (
    tenantId: number
  ): Promise<ISmsNotificationMeta[]> => {
    return SMSNotificationsConfig.map((notification) => {
      return this.transformSmsNotifConfigToMeta(tenantId, notification);
    });
  };

  /**
   * Edits/Mutates the sms notification message text.
   * @param {number} tenantId - Tenant id.
   * @param {IEditSmsNotificationDTO} editSmsNotificationDTO - Edit SMS notification DTO.
   */
  public editSmsNotificationMessage = (
    tenantId: number,
    editDTO: IEditSmsNotificationDTO
  ): ISmsNotificationMeta => {
    const settings = this.tenancy.settings(tenantId);

    const notification = this.getSmsNotificationMeta(
      tenantId,
      editDTO.notificationKey
    );
    const group = 'sms-notification';

    if (editDTO.messageText) {
      this.validateSmsMessageVariables(
        editDTO.messageText,
        notification.allowedVariables
      );
      settings.set({
        key: `sms-message.${editDTO.notificationKey}`,
        value: editDTO.messageText,
        group,
      });
    }
    if (!isUndefined(editDTO.isNotificationEnabled)) {
      settings.set({
        key: `sms-notification-enable.${editDTO.notificationKey}`,
        value: editDTO.isNotificationEnabled,
        group,
      });
    }
    return notification;
  };

  /**
   * Vaidates the sms notification key existance.
   * @param {string} notificationKey
   */
  private validateSmsNotificationExists = (
    notificationDefined: ISmsNotificationDefined | null
  ): void => {
    if (!notificationDefined) {
      throw new ServiceError(ERRORS.SMS_NOTIFICATION_KEY_NOT_FOUND);
    }
  };

  /**
   * Retrieve unspported message arguments.
   * @param {string} smsMessage - SMS message.
   * @param {string[]} args -
   * @returns {string[]}
   */
  private getUnsupportedMessageArgs = (
    smsMessage: string,
    args: string[]
  ): string[] => {
    const matchedVariables = smsMessage.match(/\{(.*?)\}/g).map((matched) => {
      return matched.replace('{', '').replace('}', '');
    });
    const invalidVariables = matchedVariables.filter(
      (variable) => args.indexOf(variable) === -1
    );
    return invalidVariables;
  };

  /**
   * Validates the sms message variables.
   * @param {string} smsMessage
   * @param {string[]} args
   */
  private validateSmsMessageVariables(
    smsMessage: string,
    allowedVariables: ISmsNotificationAllowedVariable[]
  ) {
    const allowedVariablesKeys = allowedVariables.map(
      (allowed) => allowed.variable
    );
    const unsupportedArgs = this.getUnsupportedMessageArgs(
      smsMessage,
      allowedVariablesKeys
    );

    if (unsupportedArgs.length > 0) {
      throw new ServiceError(ERRORS.UNSUPPORTED_SMS_MESSAGE_VARIABLES, null, {
        unsupportedArgs,
      });
    }
  }
}
