/*
 * 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/>.
 */
import {Client} from '@elastic/elasticsearch';
import {SearchRequest} from '@elastic/elasticsearch/lib/api/types.js';
import {
  SCMonitoringConfiguration,
  SCMonitoringLogAction,
  SCMonitoringMailAction,
  SCMonitoringMaximumLengthCondition,
  SCMonitoringMinimumLengthCondition,
  SCThings,
} from '@openstapps/core';
import {Logger} from '@openstapps/logger';
import cron from 'node-cron';
import {MailQueue} from '../../notification/mail-queue.js';

/**
 * Check if the given condition fails on the given number of results and the condition
 * @param condition condition
 * @param total number of results
 */
function conditionFails(
  condition: SCMonitoringMaximumLengthCondition | SCMonitoringMinimumLengthCondition,
  total: number,
) {
  if (condition.type === 'MaximumLength') {
    return maxConditionFails(condition.length, total);
  }

  return minConditionFails(condition.length, total);
}

/**
 * Check if the min condition fails
 * @param minimumLength Minimal length allowed
 * @param total Number of results
 */
function minConditionFails(minimumLength: number, total: number) {
  return minimumLength > total;
}

/**
 * Check if the max condition fails
 * @param maximumLength Maximal length allowed
 * @param total Number of results
 */
function maxConditionFails(maximumLength: number, total: number) {
  return maximumLength < total;
}

/**
 * Run all the given actions
 * @param actions actions to perform
 * @param watcherName name of watcher that wants to perform them
 * @param triggerName name of trigger that triggered the watcher
 * @param total total number of results of the query
 * @param mailQueue mailQueue to execute mail actions
 */
function runActions(
  actions: Array<SCMonitoringLogAction | SCMonitoringMailAction>,
  watcherName: string,
  triggerName: string,
  total: number,
  mailQueue: MailQueue,
) {
  for (const action of actions) {
    void (action.type === 'log'
      ? Logger.error(
          action.prefix,
          `Watcher '${watcherName}' failed. Watcher was triggered by '${triggerName}'`,
          `Found ${total} hits instead`,
          action.message,
        )
      : mailQueue.push({
          subject: action.subject,
          text: `Watcher '${watcherName}' failed. Watcher was triggered by '${triggerName}'
          ${action.message} Found ${total} hits instead`,
          to: action.recipients,
        }));
  }
}

/**
 * Set up the triggers for the configured watchers
 * @param monitoringConfig configuration of the monitoring
 * @param esClient elasticsearch client
 * @param mailQueue mailQueue for mail actions
 */
export async function setUp(
  monitoringConfig: SCMonitoringConfiguration,
  esClient: Client,
  mailQueue: MailQueue,
) {
  // set up Watches
  for (const watcher of monitoringConfig.watchers) {
    // make a schedule for each trigger
    for (const trigger of watcher.triggers) {
      switch (trigger.executionTime) {
        case 'hourly': {
          trigger.executionTime = '5 * * * *';
          break;
        }
        case 'daily': {
          trigger.executionTime = '5 0 * * *';
          break;
        }
        case 'weekly': {
          trigger.executionTime = '5 0 * * 0';
          break;
        }
        case 'monthly': {
          trigger.executionTime = '5 0 1 * *';
        }
      }

      cron.schedule(trigger.executionTime, async () => {
        // execute watch (search->condition->action)
        const result = await esClient.search<SCThings>(watcher.query as SearchRequest);

        // check conditions
        const total =
          typeof result.hits.total === 'number' ? result.hits.total : result.hits.total?.value ?? -1;

        for (const condition of watcher.conditions) {
          if (conditionFails(condition, total)) {
            runActions(watcher.actions, watcher.name, trigger.name, total, mailQueue);
          }
        }
      });
    }
  }

  Logger.log(`Scheduled ${monitoringConfig.watchers.length} watches`);
}
