<?php declare(strict_types = 1);

namespace MailPoet\Cron\Workers;

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


use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository;
use MailPoet\Settings\SettingsController;
use MailPoetVendor\Carbon\Carbon;

/**
 * Purges old rows from scheduled_task_subscribers for completed sending tasks.
 *
 * Runs 4 times per day in random 6-hour slots (0–5h, 6–11h, 12–17h, 18–23h).
 * Each run loops in batches: first selects up to TASK_BATCH_SIZE completed tasks
 * older than the configured retention period that still have subscriber rows,
 * then deletes up to ROW_BATCH_SIZE rows per iteration. The loop continues until
 * fewer rows are deleted than the limit or MAX_EXECUTION_TIME is exceeded.
 * A 100ms pause between iterations throttles I/O on shared hosting.
 *
 * Retention is controlled by the 'sending_status_retention_days' setting.
 * Empty string means "Never" — the worker skips all deletions.
 */
class SendingTaskSubscribersCleanup extends SimpleWorker {
  const TASK_TYPE = 'sending_task_subscribers_cleanup';
  const TASK_BATCH_SIZE = 200;
  const ROW_BATCH_SIZE = 10000;
  const MAX_EXECUTION_TIME = 30;
  const SUPPORT_MULTIPLE_INSTANCES = false;

  /** @var ScheduledTaskSubscribersRepository */
  private $scheduledTaskSubscribersRepository;

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

  public function __construct(
    ScheduledTaskSubscribersRepository $scheduledTaskSubscribersRepository,
    SettingsController $settings
  ) {
    $this->scheduledTaskSubscribersRepository = $scheduledTaskSubscribersRepository;
    $this->settings = $settings;
    parent::__construct();
  }

  public function processTaskStrategy(ScheduledTaskEntity $task, $timer) {
    $retentionDays = (int)$this->settings->get('sending_status_retention_days', '');

    if ($retentionDays <= 0) {
      return true;
    }

    $startTime = microtime(true);

    do {
      $this->cronHelper->enforceExecutionLimit($timer);

      $deleted = $this->scheduledTaskSubscribersRepository->purgeOldTaskSubscribers(
        $retentionDays,
        self::TASK_BATCH_SIZE,
        self::ROW_BATCH_SIZE
      );

      if (
        $deleted === 0 ||
          (microtime(true) - $startTime) > self::MAX_EXECUTION_TIME
      ) {
        break;
      }

      usleep(100000);
    } while (true);

    return true;
  }

  public function schedule() {
    $baseDate = Carbon::now()->millisecond(0)->startOfDay();

    for ($slot = 0; $slot < 4; $slot++) {
      $hour = $slot * 6 + mt_rand(0, 5);
      $minute = mt_rand(0, 59);
      $second = mt_rand(0, 59);

      $scheduleDate = clone $baseDate;
      $scheduleDate->setTime($hour, $minute, $second);

      if ($scheduleDate->isPast()) {
        $scheduleDate->addDay();
      }

      $this->cronWorkerScheduler->scheduleMultiple(static::TASK_TYPE, $scheduleDate);
    }
  }

  public function getNextRunDate() {
    $date = Carbon::now()->millisecond(0);
    $timeSlot = mt_rand(0, 3);
    $hour = $timeSlot * 6 + mt_rand(0, 5);
    $minute = mt_rand(0, 59);

    $date->setTime($hour, $minute, mt_rand(0, 59));

    if ($date->isPast()) {
      $date->addDay();
    }

    return $date;
  }
}