<?php

namespace MailPoet\Cron\Triggers;

if (!defined('ABSPATH')) exit;


use MailPoet\Config\ServicesChecker;
use MailPoet\Cron\CronHelper;
use MailPoet\Cron\Supervisor;
use MailPoet\Cron\Workers\AuthorizedSendingEmailsCheck;
use MailPoet\Cron\Workers\Beamer as BeamerWorker;
use MailPoet\Cron\Workers\Bounce as BounceWorker;
use MailPoet\Cron\Workers\InactiveSubscribers;
use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck as PremiumKeyCheckWorker;
use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
use MailPoet\Cron\Workers\NewsletterTemplateThumbnails;
use MailPoet\Cron\Workers\ReEngagementEmailsScheduler;
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
use MailPoet\Cron\Workers\SendingQueue\Migration as MigrationWorker;
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
use MailPoet\Cron\Workers\StatsNotifications\AutomatedEmails;
use MailPoet\Cron\Workers\StatsNotifications\Worker as StatsNotificationsWorker;
use MailPoet\Cron\Workers\SubscriberLinkTokens;
use MailPoet\Cron\Workers\SubscribersCountCacheRecalculation;
use MailPoet\Cron\Workers\SubscribersEmailCount;
use MailPoet\Cron\Workers\SubscribersEngagementScore;
use MailPoet\Cron\Workers\SubscribersLastEngagement;
use MailPoet\Cron\Workers\SubscribersStatsReport;
use MailPoet\Cron\Workers\UnsubscribeTokens;
use MailPoet\Cron\Workers\WooCommercePastOrders;
use MailPoet\Cron\Workers\WooCommerceSync as WooCommerceSyncWorker;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Mailer\MailerLog;
use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
use MailPoet\Services\Bridge;
use MailPoet\Settings\SettingsController;
use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\Doctrine\ORM\EntityManager;

class WordPress {
  const SCHEDULED_IN_THE_PAST = 'past';
  const SCHEDULED_IN_THE_FUTURE = 'future';

  const RUN_INTERVAL = -1; // seconds
  const LAST_RUN_AT_SETTING = 'cron_trigger_wordpress.last_run_at';

  private $tasksCounts;

  /** @var CronHelper */
  private $cronHelper;

  /** @var Supervisor  */
  private $supervisor;

  /** @var SettingsController */
  private $settings;

  /** @var WPFunctions */
  private $wp;

  /** @var ServicesChecker */
  private $serviceChecker;

  /** @var ScheduledTasksRepository */
  private $scheduledTasksRepository;

  /** @var EntityManager */
  private $entityManager;

  public function __construct(
    CronHelper $cronHelper,
    Supervisor $supervisor,
    SettingsController $settings,
    ServicesChecker $serviceChecker,
    WPFunctions $wp,
    ScheduledTasksRepository $scheduledTasksRepository,
    EntityManager $entityManager
  ) {
    $this->supervisor = $supervisor;
    $this->settings = $settings;
    $this->wp = $wp;
    $this->cronHelper = $cronHelper;
    $this->serviceChecker = $serviceChecker;
    $this->scheduledTasksRepository = $scheduledTasksRepository;
    $this->entityManager = $entityManager;
  }

  public function run() {
    if (!$this->checkRunInterval()) {
      return false;
    }
    if (!$this->checkExecutionRequirements()) {
      $this->stop();
      return;
    }

    $this->supervisor->init();
    return $this->supervisor->checkDaemon();
  }

  private function checkRunInterval() {
    $runInterval = $this->wp->applyFilters('mailpoet_cron_trigger_wordpress_run_interval', self::RUN_INTERVAL);
    if ($runInterval === -1) {
      return true;
    }
    $lastRunAt = (int)$this->settings->get(self::LAST_RUN_AT_SETTING, 0);
    $runIntervalElapsed = (time() - $lastRunAt) >= $runInterval;
    if ($runIntervalElapsed) {
      $this->settings->set(self::LAST_RUN_AT_SETTING, time());
      return true;
    }
    return false;
  }

  public static function resetRunInterval() {
    $settings = SettingsController::getInstance();
    $settings->set(self::LAST_RUN_AT_SETTING, 0);
  }

  public function checkExecutionRequirements() {
    $this->loadTasksCounts();

    // migration
    $migrationDisabled = $this->settings->get('cron_trigger.method') === 'none';
    $migrationDueTasks = $this->getTasksCount([
      'type' => MigrationWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    $migrationCompletedTasks = $this->getTasksCount([
      'type' => MigrationWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST, self::SCHEDULED_IN_THE_FUTURE],
      'status' => [ScheduledTaskEntity::STATUS_COMPLETED],
    ]);
    $migrationFutureTasks = $this->getTasksCount([
      'type' => MigrationWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_FUTURE],
      'status' => [ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // sending queue
    $scheduledQueues = SchedulerWorker::getScheduledQueues();
    $runningQueues = $this->scheduledTasksRepository->findRunningSendingTasks(SendingQueueWorker::TASK_BATCH_SIZE);
    $sendingLimitReached = MailerLog::isSendingLimitReached();
    $sendingIsPaused = MailerLog::isSendingPaused();
    // sending service
    $mpSendingEnabled = Bridge::isMPSendingServiceEnabled();
    // bounce sync
    $bounceDueTasks = $this->getTasksCount([
      'type' => BounceWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    $bounceFutureTasks = $this->getTasksCount([
      'type' => BounceWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_FUTURE],
      'status' => [ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // sending service key check
    $msskeycheckDueTasks = $this->getTasksCount([
      'type' => SendingServiceKeyCheckWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    $msskeycheckFutureTasks = $this->getTasksCount([
      'type' => SendingServiceKeyCheckWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_FUTURE],
      'status' => [ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // premium key check
    $premiumKeySpecified = Bridge::isPremiumKeySpecified();
    $premiumKeycheckDueTasks = $this->getTasksCount([
      'type' => PremiumKeyCheckWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    $premiumKeycheckFutureTasks = $this->getTasksCount([
      'type' => PremiumKeyCheckWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_FUTURE],
      'status' => [ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // subscriber stats
    $isAnyKeyValid = $this->serviceChecker->getAnyValidKey();
    $statsReportDueTasks = $this->getTasksCount([
      'type' => SubscribersStatsReport::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    $statsReportFutureTasks = $this->getTasksCount([
      'type' => SubscribersStatsReport::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_FUTURE],
      'status' => [ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // stats notifications
    $statsNotificationsTasks = $this->getTasksCount([
      'type' => StatsNotificationsWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // stats notifications for auto emails
    $autoStatsNotificationsTasks = $this->getTasksCount([
      'type' => AutomatedEmails::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // subscribers emails count
    $subscribersEmailsCount = $this->getTasksCount([
      'type' => SubscribersEmailCount::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // inactive subscribers check
    $inactiveSubscribersTasks = $this->getTasksCount([
      'type' => InactiveSubscribers::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // unsubscribe tokens check
    $unsubscribeTokensTasks = $this->getTasksCount([
      'type' => UnsubscribeTokens::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // subscriber link tokens check
    $subscriberLinkTokensTasks = $this->getTasksCount([
      'type' => SubscriberLinkTokens::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // WooCommerce sync
    $wooCommerceSyncTasks = $this->getTasksCount([
      'type' => WooCommerceSyncWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    // Beamer
    $beamerDueChecks = $this->getTasksCount([
      'type' => BeamerWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);
    $beamerFutureChecks = $this->getTasksCount([
      'type' => BeamerWorker::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_FUTURE],
      'status' => [ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);

    // Authorized email addresses check
    $authorizedEmailAddressesTasks = $this->getTasksCount([
      'type' => AuthorizedSendingEmailsCheck::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);

    // WooCommerce past orders revenues sync
    $wooCommercePastOrdersTasks = $this->getTasksCount([
      'type' => WooCommercePastOrders::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);

    // subscriber engagement score
    $subscriberEngagementScoreTasks = $this->getTasksCount([
      'type' => SubscribersEngagementScore::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);

    // subscriber counts cache recalculation
    $subscribersCountCacheRecalculationTasks = $this->getTasksCount([
      'type' => SubscribersCountCacheRecalculation::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);

    // subscriber last engagement
    $subscribersLastEngagementTasks = $this->getTasksCount([
      'type' => SubscribersLastEngagement::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);

    // re-engagement emails scheduling;
    $subscribersReEngagementSchedulingTasks = $this->getTasksCount([
      'type' => ReEngagementEmailsScheduler::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);

    // newsletter template thumbnails
    $newsletterTemplateThumbnailsTasks = $this->getTasksCount([
      'type' => NewsletterTemplateThumbnails::TASK_TYPE,
      'scheduled_in' => [self::SCHEDULED_IN_THE_PAST],
      'status' => ['null', ScheduledTaskEntity::STATUS_SCHEDULED],
    ]);

    // check requirements for each worker
    $sendingQueueActive = (($scheduledQueues || $runningQueues) && !$sendingLimitReached && !$sendingIsPaused);
    $bounceSyncActive = ($mpSendingEnabled && ($bounceDueTasks || !$bounceFutureTasks));
    $sendingServiceKeyCheckActive = ($mpSendingEnabled && ($msskeycheckDueTasks || !$msskeycheckFutureTasks));
    $premiumKeyCheckActive = ($premiumKeySpecified && ($premiumKeycheckDueTasks || !$premiumKeycheckFutureTasks));
    $subscribersStatsReportActive = ($isAnyKeyValid && ($statsReportDueTasks || !$statsReportFutureTasks));
    $migrationActive = !$migrationDisabled && ($migrationDueTasks || (!$migrationCompletedTasks && !$migrationFutureTasks));
    $beamerActive = $beamerDueChecks || !$beamerFutureChecks;

    return (
      $migrationActive
      || $sendingQueueActive
      || $bounceSyncActive
      || $sendingServiceKeyCheckActive
      || $premiumKeyCheckActive
      || $subscribersStatsReportActive
      || $statsNotificationsTasks
      || $autoStatsNotificationsTasks
      || $subscribersEmailsCount
      || $inactiveSubscribersTasks
      || $wooCommerceSyncTasks
      || $authorizedEmailAddressesTasks
      || $beamerActive
      || $wooCommercePastOrdersTasks
      || $unsubscribeTokensTasks
      || $subscriberLinkTokensTasks
      || $subscriberEngagementScoreTasks
      || $subscribersCountCacheRecalculationTasks
      || $subscribersLastEngagementTasks
      || $subscribersReEngagementSchedulingTasks
      || $newsletterTemplateThumbnailsTasks
    );
  }

  public function stop() {
    $cronDaemon = $this->cronHelper->getDaemon();
    if ($cronDaemon) {
      $this->cronHelper->deactivateDaemon($cronDaemon);
    }
  }

  private function loadTasksCounts() {
    $scheduledTasksTableName = $this->entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();
    $sql = "
      select
        type,
        status,
        count(*) as count,
        case when scheduled_at <= :now then :past else :future end as scheduled_in
      from $scheduledTasksTableName
      where deleted_at is null AND (status != :statusCompleted OR status IS NULL OR `type` = :typeMigration)
      group by type, status, scheduled_in";

    $stmt = $this->entityManager->getConnection()->prepare($sql);
    $stmt->bindValue('now', date('Y-m-d H:i:s', $this->wp->currentTime('timestamp')));
    $stmt->bindValue('past', self::SCHEDULED_IN_THE_PAST);
    $stmt->bindValue('future', self::SCHEDULED_IN_THE_FUTURE);
    $stmt->bindValue('statusCompleted', ScheduledTaskEntity::STATUS_COMPLETED);
    $stmt->bindValue('typeMigration', MigrationWorker::TASK_TYPE);
    $rows = $stmt->executeQuery()->fetchAllAssociative();

    $this->tasksCounts = [];
    foreach ($rows as $r) {
      if (empty($this->tasksCounts[$r['type']])) {
        $this->tasksCounts[$r['type']] = [];
      }
      if (empty($this->tasksCounts[$r['type']][$r['scheduled_in']])) {
        $this->tasksCounts[$r['type']][$r['scheduled_in']] = [];
      }
      $this->tasksCounts[$r['type']][$r['scheduled_in']][$r['status'] ?: 'null'] = $r['count'];
    }
  }

  private function getTasksCount(array $options) {
    $count = 0;
    $type = $options['type'];
    foreach ($options['scheduled_in'] as $scheduledIn) {
      foreach ($options['status'] as $status) {
        if (!empty($this->tasksCounts[$type][$scheduledIn][$status])) {
          $count += $this->tasksCounts[$type][$scheduledIn][$status];
        }
      }
    }
    return $count;
  }
}
