<?php declare(strict_types = 1);

namespace MailPoet\Subscribers;

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


use MailPoet\Cron\Workers\BulkConfirmationEmailResend;
use MailPoet\Entities\LogEntity;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Listing\ListingDefinition;
use MailPoet\Logging\LoggerFactory;
use MailPoet\Logging\LogRepository;
use MailPoet\Settings\SettingsController;
use MailPoet\Util\Helpers;
use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\Carbon\Carbon;
use MailPoetVendor\Doctrine\ORM\EntityManager;

class BulkConfirmationEmailResender {
  public const BULK_CONFIRMATION_RESEND_LIMIT = 20;
  public const RECENT_CONFIRMATION_RESEND_INTERVAL_DAYS = 7;
  public const BULK_CONFIRMATION_MAX_SUBSCRIBER_AGE_DAYS = 90;

  private const AUDIT_LOG_MESSAGE_QUEUED = 'Bulk confirmation email resend queued.';
  private const LOG_LEVEL_INFO = 200;

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

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

  /** @var SubscriberListingRepository */
  private $subscriberListingRepository;

  /** @var LogRepository */
  private $logRepository;

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

  public function __construct(
    WPFunctions $wp,
    SettingsController $settings,
    SubscriberListingRepository $subscriberListingRepository,
    LogRepository $logRepository,
    EntityManager $entityManager
  ) {
    $this->wp = $wp;
    $this->settings = $settings;
    $this->subscriberListingRepository = $subscriberListingRepository;
    $this->logRepository = $logRepository;
    $this->entityManager = $entityManager;
  }

  public function canCurrentUserResend(): bool {
    return $this->wp->currentUserCan('manage_options');
  }

  public function isSignupConfirmationEnabled(): bool {
    return (bool)$this->settings->get('signup_confirmation.enabled', true);
  }

  public function getConfirmationDisabledMessage(): string {
    $errorMessage = __('Sign-up confirmation is disabled in your [link]MailPoet settings[/link]. Please enable it to resend confirmation emails or update your subscriber’s status manually.', 'mailpoet');
    return Helpers::replaceLinkTags($errorMessage, 'admin.php?page=mailpoet-settings#/signup');
  }

  /**
   * @param array<string, mixed> $requestData
   * @return array{selected_count: int, eligible_count: int, queued_count: int, skipped_count: int, skipped_by_reason: array<string, int>, task_id: int|null, message: string}
   */
  public function queue(ListingDefinition $definition, array $requestData): array {
    $now = Carbon::now()->millisecond(0);
    $queueData = $this->subscriberListingRepository->getConfirmationEmailResendQueueData(
      $definition,
      (clone $now)->subDays(self::RECENT_CONFIRMATION_RESEND_INTERVAL_DAYS),
      (clone $now)->subDays(self::BULK_CONFIRMATION_MAX_SUBSCRIBER_AGE_DAYS),
      ConfirmationEmailMailer::MAX_CONFIRMATION_EMAILS,
      self::BULK_CONFIRMATION_RESEND_LIMIT,
      $this->hasExplicitSelection($requestData)
    );

    $queuedIds = $queueData['queued_ids'];
    $task = null;
    if ($queuedIds) {
      $task = $this->createTask($queuedIds, $queueData, $requestData, $now);
    } else {
      $this->saveQueueAuditLog(null, $queueData, $requestData);
    }

    $queuedCount = count($queuedIds);
    return [
      'selected_count' => $queueData['selected_count'],
      'eligible_count' => $queueData['eligible_count'],
      'queued_count' => $queuedCount,
      'skipped_count' => max(0, $queueData['selected_count'] - $queuedCount),
      'skipped_by_reason' => $queueData['skipped_by_reason'],
      'task_id' => $task instanceof ScheduledTaskEntity ? $task->getId() : null,
      'message' => $queuedCount > 0
        ? __('Confirmation emails are being resent.', 'mailpoet')
        : __(
          'No confirmation emails were resent. The selected subscribers could not receive another confirmation email right now.',
          'mailpoet'
        ),
    ];
  }

  /**
   * @param int[] $queuedIds
   * @param array{selected_count: int, eligible_count: int, queued_ids: int[], skipped_by_reason: array<string, int>} $queueData
   * @param array<string, mixed> $requestData
   */
  private function createTask(array $queuedIds, array $queueData, array $requestData, Carbon $now): ScheduledTaskEntity {
    $task = new ScheduledTaskEntity();
    $this->entityManager->transactional(function (EntityManager $entityManager) use ($task, $queuedIds, $queueData, $requestData, $now) {
      $task->setType(BulkConfirmationEmailResend::TASK_TYPE);
      $task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
      $task->setScheduledAt($now);
      $task->setPriority(ScheduledTaskEntity::PRIORITY_HIGH);
      $task->setMeta([
        'requested_by' => (int)$this->wp->getCurrentUserId(),
        'selected_count' => $queueData['selected_count'],
        'eligible_count' => $queueData['eligible_count'],
        'queued_count' => count($queuedIds),
        'skipped_by_reason' => $queueData['skipped_by_reason'],
        'constants' => $this->getAuditConstants(),
        'request' => $this->getRequestSummary($requestData),
      ]);
      $entityManager->persist($task);

      foreach ($queuedIds as $subscriberId) {
        /** @var SubscriberEntity $subscriberReference */
        $subscriberReference = $entityManager->getReference(SubscriberEntity::class, $subscriberId);
        $entityManager->persist(new ScheduledTaskSubscriberEntity(
          $task,
          $subscriberReference
        ));
      }

      $entityManager->flush();
      $this->saveQueueAuditLog($task, $queueData, $requestData);
    });

    return $task;
  }

  /**
   * @param array{selected_count: int, eligible_count: int, queued_ids: int[], skipped_by_reason: array<string, int>} $queueData
   * @param array<string, mixed> $requestData
   */
  private function saveQueueAuditLog(?ScheduledTaskEntity $task, array $queueData, array $requestData): void {
    $log = new LogEntity();
    $log->setName(LoggerFactory::TOPIC_API);
    $log->setLevel(self::LOG_LEVEL_INFO);
    $log->setMessage(self::AUDIT_LOG_MESSAGE_QUEUED);
    $log->setRawMessage(self::AUDIT_LOG_MESSAGE_QUEUED);
    $log->setContext([
      'action' => 'bulk_confirmation_email_resend_queued',
      'requested_by' => (int)$this->wp->getCurrentUserId(),
      'task_id' => $task instanceof ScheduledTaskEntity ? $task->getId() : null,
      'selected_count' => $queueData['selected_count'],
      'eligible_count' => $queueData['eligible_count'],
      'queued_count' => count($queueData['queued_ids']),
      'skipped_by_reason' => $queueData['skipped_by_reason'],
      'constants' => $this->getAuditConstants(),
      'request' => $this->getRequestSummary($requestData),
    ]);
    $this->logRepository->saveLog($log);
  }

  /**
   * @return array<string, int>
   */
  private function getAuditConstants(): array {
    return [
      'bulk_confirmation_resend_limit' => self::BULK_CONFIRMATION_RESEND_LIMIT,
      'recent_confirmation_resend_interval_days' => self::RECENT_CONFIRMATION_RESEND_INTERVAL_DAYS,
      'bulk_confirmation_max_subscriber_age_days' => self::BULK_CONFIRMATION_MAX_SUBSCRIBER_AGE_DAYS,
      'max_confirmation_emails' => ConfirmationEmailMailer::MAX_CONFIRMATION_EMAILS,
    ];
  }

  /**
   * @param array<string, mixed> $requestData
   * @return array<string, mixed>
   */
  private function getRequestSummary(array $requestData): array {
    $listing = isset($requestData['listing']) && is_array($requestData['listing']) ? $requestData['listing'] : [];
    return [
      'group' => $listing['group'] ?? null,
      'has_selection' => !empty($listing['selection']),
      'selection_count' => isset($listing['selection']) && is_array($listing['selection']) ? count($listing['selection']) : 0,
      'has_search' => !empty($listing['search']),
      'filter_keys' => isset($listing['filter']) && is_array($listing['filter']) ? array_keys($listing['filter']) : [],
    ];
  }

  /**
   * @param array<string, mixed> $requestData
   */
  private function hasExplicitSelection(array $requestData): bool {
    $listing = isset($requestData['listing']) && is_array($requestData['listing']) ? $requestData['listing'] : [];
    return array_key_exists('selection', $listing);
  }
}
