/*
 * Copyright (C) 2019 StApps
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.nse along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */
import {Logger, SMTP} from '@openstapps/logger';
import {MailOptions} from 'nodemailer/lib/sendmail-transport';
import Queue from 'promise-queue';
/**
 * A queue that can send mails in serial
 */
export class MailQueue {
  /**
   * Number of allowed verification attempts after which the initialization of transport fails
   */
  static readonly MAX_VERIFICATION_ATTEMPTS = 5;

  /**
   * Number of milliseconds after which verification check should be repeated
   */
  static readonly VERIFICATION_TIMEOUT = 5000;

  /**
   * A queue that saves mails, before the transport is ready. When
   * the transport gets ready this mails are getting pushed in to
   * the normal queue.
   */
  dryQueue: MailOptions[];

  /**
   * A queue that saves mails, that are being sent in series
   */
  queue: Queue;

  /**
   * A counter for the number of verifications that failed
   */
  verificationCounter: number;

  /**
   * Creates a mail queue
   * @param transport Transport which is used for sending mails
   */
  constructor(private readonly transport: SMTP) {
    this.queue = new Queue(1);

    // this queue saves all request when the transport is not ready yet
    this.dryQueue = [];

    this.verificationCounter = 0;

    // if the transport can be verified it should check if it was done...
    this.checkForVerification();
  }

  /**
   * Adds a mail into the queue so it gets send when the queue is ready
   * @param mail Information required for sending a mail
   */
  private async addToQueue(mail: MailOptions) {
    return this.queue.add<string>(() => this.transport.sendMail(mail));
  }

  /**
   * Verify the given transport
   */
  private checkForVerification() {
    if (this.verificationCounter >= MailQueue.MAX_VERIFICATION_ATTEMPTS) {
      throw new Error('Failed to initialize the SMTP transport for the mail queue');
    }

    if (this.transport.isVerified()) {
      Logger.ok('Transport for mail queue was verified. We can send mails now');
      // if the transport finally was verified send all our mails from the dry queue
      for (const mail of this.dryQueue) {
        void this.addToQueue(mail);
      }
    } else {
      this.verificationCounter++;
      setTimeout(() => {
        Logger.warn('Transport not verified yet. Trying to send mails here...');
        this.checkForVerification();
      }, MailQueue.VERIFICATION_TIMEOUT);
    }
  }

  /**
   * Push a mail into the queue so it gets send when the queue is ready
   * @param mail Information required for sending a mail
   */
  public async push(mail: MailOptions) {
    if (this.transport.isVerified()) {
      await this.addToQueue(mail);
    } else {
      // the transport has verification, but is not verified yet
      // push to a dry queue which gets pushed to the real queue when the transport is verified
      this.dryQueue.push(mail);
    }
  }
}
