<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing

namespace MailPoet\Subscribers;

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


use MailPoet\Entities\SubscriberEntity;
use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
use MailPoet\Segments\SegmentsRepository;
use MailPoet\Settings\SettingsController;
use MailPoet\Subscribers\ConfirmationEmailResolver;
use MailPoet\Util\Helpers;
use MailPoetVendor\Carbon\Carbon;

class SubscriberActions {
  /** @var SettingsController */
  private $settings;

  /** @var NewSubscriberNotificationMailer */
  private $newSubscriberNotificationMailer;

  /** @var ConfirmationEmailMailer */
  private $confirmationEmailMailer;

  /** @var WelcomeScheduler */
  private $welcomeScheduler;

  /** @var SubscriberSaveController */
  private $subscriberSaveController;

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

  /** @var SubscriberSegmentRepository */
  private $subscriberSegmentRepository;

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

  /** @var ConfirmationEmailResolver */
  private $confirmationEmailResolver;

  public function __construct(
    SettingsController $settings,
    NewSubscriberNotificationMailer $newSubscriberNotificationMailer,
    ConfirmationEmailMailer $confirmationEmailMailer,
    WelcomeScheduler $welcomeScheduler,
    SegmentsRepository $segmentsRepository,
    SubscriberSaveController $subscriberSaveController,
    SubscribersRepository $subscribersRepository,
    SubscriberSegmentRepository $subscriberSegmentRepository,
    ConfirmationEmailResolver $confirmationEmailResolver
  ) {
    $this->settings = $settings;
    $this->newSubscriberNotificationMailer = $newSubscriberNotificationMailer;
    $this->confirmationEmailMailer = $confirmationEmailMailer;
    $this->welcomeScheduler = $welcomeScheduler;
    $this->subscriberSaveController = $subscriberSaveController;
    $this->subscribersRepository = $subscribersRepository;
    $this->subscriberSegmentRepository = $subscriberSegmentRepository;
    $this->segmentsRepository = $segmentsRepository;
    $this->confirmationEmailResolver = $confirmationEmailResolver;
  }

  /**
   * Returns SubscriberEntity and associative array with some metadata related to the subscription (e.g. ['confirmationEmailResult' => $exception])
   * @param array $subscriberData Subscriber data (email, first_name, last_name, etc.)
   * @param array $segmentIds IDs of segments to subscribe to
   * @return array{0: SubscriberEntity, 1: array{confirmationEmailResult: bool|\Exception, error?: string}}
   */
  public function subscribe($subscriberData = [], $segmentIds = []): array {
    // filter out keys from the subscriber_data array
    // that should not be editable when subscribing
    $subscriberData = $this->subscriberSaveController->filterOutReservedColumns($subscriberData);

    $signupConfirmationEnabled = (bool)$this->settings->get(
      'signup_confirmation.enabled'
    );

    $subscriberData['subscribed_ip'] = Helpers::getIP();

    $subscriber = $this->subscribersRepository->findOneBy(['email' => $subscriberData['email']]);
    $previousStatus = $subscriber instanceof SubscriberEntity ? $subscriber->getStatus() : null;
    $wasAlreadySubscribed = $previousStatus === SubscriberEntity::STATUS_SUBSCRIBED;
    $isUnconfirmedResubscription = (
      $subscriber instanceof SubscriberEntity
      && $subscriber->getStatus() === SubscriberEntity::STATUS_UNCONFIRMED
    );
    if (!$subscriber && !isset($subscriberData['source'])) {
      $subscriberData['source'] = Source::FORM;
    }

    if (!$subscriber || !$signupConfirmationEnabled) {
      // create new subscriber or update if no confirmation is required
      $subscriber = $this->subscriberSaveController->createOrUpdate($subscriberData, $subscriber);
      // custom fields should use the same approach as the subscriber main data that means to wait on confirmation
      $this->subscriberSaveController->updateCustomFields($subscriberData, $subscriber);
    } else if ($wasAlreadySubscribed) {
      $subscriber->setUnconfirmedData(null);
    } else {
      // store subscriber data to be updated after confirmation
      $unconfirmedData = $this->subscriberSaveController->filterOutReservedColumns($subscriberData);
      $unconfirmedData = json_encode($unconfirmedData);
      $subscriber->setUnconfirmedData($unconfirmedData ?: null);
    }

    // restore trashed subscriber
    if ($subscriber->getDeletedAt()) {
      $subscriber->setDeletedAt(null);
    }

    // set status depending on signup confirmation setting
    if ($subscriber->getStatus() !== SubscriberEntity::STATUS_SUBSCRIBED) {
      if ($signupConfirmationEnabled === true) {
        $subscriber->setStatus(SubscriberEntity::STATUS_UNCONFIRMED);
        if ($previousStatus !== SubscriberEntity::STATUS_UNCONFIRMED) {
          $subscriber->setConfirmationsCount(0);
          $subscriber->setLastConfirmationEmailSentAt(null);
          $subscriber->setLastSubscribedAt(Carbon::now()->millisecond(0));
        }
      } else {
        $subscriber->setStatus(SubscriberEntity::STATUS_SUBSCRIBED);
      }
    }

    $this->subscribersRepository->flush();

    $metaData = ['confirmationEmailResult' => false];
    // link subscriber to segments
    $segments = $this->segmentsRepository->findByIds($segmentIds);
    $this->subscriberSegmentRepository->subscribeToSegments($subscriber, $segments);

    // resolve confirmation settings from segment-level overrides
    [$confirmationEmailId, $confirmationPageId] = $this->confirmationEmailResolver->resolveFromSegments($segments);

    try {
      if (
        $signupConfirmationEnabled
        && $isUnconfirmedResubscription
        && $subscriber->getConfirmationsCount() >= ConfirmationEmailMailer::MAX_CONFIRMATION_EMAILS
      ) {
        $metaData['error'] = $this->getMaxConfirmationEmailsReachedError();
      } else if (!$wasAlreadySubscribed) {
        $metaData['confirmationEmailResult'] = $this->confirmationEmailMailer->sendConfirmationEmailOnce($subscriber, $confirmationEmailId, $confirmationPageId, true);
      }
    } catch (\Exception $e) {
      $metaData['confirmationEmailResult'] = $e;
    }

    // We want to send the notification on subscribe only when signupConfirmation is disabled
    if ($signupConfirmationEnabled === false && $subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) {
      $this->newSubscriberNotificationMailer->send($subscriber, $this->segmentsRepository->findByIds($segmentIds));

      $this->welcomeScheduler->scheduleSubscriberWelcomeNotification(
        $subscriber->getId(),
        $segmentIds
      );
    }

    return [$subscriber, $metaData];
  }

  private function getMaxConfirmationEmailsReachedError(): string {
    return __('We\'ve already sent you a confirmation email. Please check your inbox or spam folder.', 'mailpoet');
  }
}
