<?php declare(strict_types = 1);

namespace MailPoet\Cron\Workers;

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


use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Segments\SegmentsRepository;
use MailPoet\Statistics\StatisticsOpensRepository;
use MailPoet\Subscribers\SubscribersRepository;
use MailPoetVendor\Carbon\Carbon;

class SubscribersEngagementScore extends SimpleWorker {
  const AUTOMATIC_SCHEDULING = true;
  const SUPPORT_MULTIPLE_INSTANCES = false;
  const BATCH_SIZE = 1000;
  const SEGMENTS_BATCH_SIZE = 100;
  const TASK_TYPE = 'subscribers_engagement_score';

  /** @var SegmentsRepository */
  private $segmentsRepository;

  /** @var StatisticsOpensRepository */
  private $statisticsOpensRepository;

  /** @var SubscribersRepository */
  private $subscribersRepository;

  public function __construct(
    SegmentsRepository $segmentsRepository,
    StatisticsOpensRepository $statisticsOpensRepository,
    SubscribersRepository $subscribersRepository
  ) {
    parent::__construct();
    $this->segmentsRepository = $segmentsRepository;
    $this->statisticsOpensRepository = $statisticsOpensRepository;
    $this->subscribersRepository = $subscribersRepository;
  }

  public function processTaskStrategy(ScheduledTaskEntity $task, $timer) {
    while ($this->recalculateSubscribers() > 0) {
      $this->cronHelper->enforceExecutionLimit($timer); // Throws exception and interrupts process if over execution limit
    }
    while ($this->recalculateSegments($timer) > 0) {
      $this->cronHelper->enforceExecutionLimit($timer);
    }
    $this->schedule();
    return true;
  }

  private function recalculateSubscribers(): int {
    $subscriberIds = $this->subscribersRepository->findIdsByUpdatedScoreNotInLastMonth(self::BATCH_SIZE);
    $this->statisticsOpensRepository->recalculateSubscribersScore($subscriberIds);
    return count($subscriberIds);
  }

  /**
   * @param float $timer
   */
  private function recalculateSegments($timer): int {
    $segments = $this->segmentsRepository->findByUpdatedScoreNotInLastDay(self::SEGMENTS_BATCH_SIZE);
    foreach ($segments as $segment) {
      $this->statisticsOpensRepository->recalculateSegmentScore($segment);
      // A single segment recalculation can be slow on large lists, so check the limit per segment,
      // not only per batch. Each processed segment is flushed immediately, so progress survives.
      $this->cronHelper->enforceExecutionLimit($timer);
    }
    return count($segments);
  }

  public function getNextRunDate() {
    // random day of the next week
    $date = Carbon::now()->millisecond(0);
    $date->addDay();
    $date->setTime(mt_rand(0, 23), mt_rand(0, 59));
    return $date;
  }
}
