<?php declare(strict_types = 1);

namespace MailPoet\EmailEditor\Integrations\MailPoet;

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


use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\NewsletterOptionEntity;
use MailPoet\Entities\NewsletterOptionFieldEntity;
use MailPoet\Entities\NewsletterSegmentEntity;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\Options\NewsletterOptionFieldsRepository;
use MailPoet\Newsletter\Options\NewsletterOptionsRepository;
use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
use MailPoet\Newsletter\Sharing\ShareVisibility;
use MailPoet\Newsletter\Url as NewsletterUrl;
use MailPoet\NotFoundException;
use MailPoet\UnexpectedValueException;
use MailPoet\Validator\Builder;
use MailPoetVendor\Doctrine\ORM\EntityManager;

class EmailApiController {
  /** @var NewslettersRepository */
  private $newsletterRepository;

  /** @var NewsletterUrl */
  private $newsletterUrl;

  /** @var NewsletterOptionFieldsRepository */
  private $newsletterOptionFieldsRepository;

  /** @var NewsletterOptionsRepository */
  private $newsletterOptionsRepository;

  /** @var NewsletterSegmentRepository */
  private $newsletterSegmentRepository;

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

  /** @var ShareVisibility */
  private $shareVisibility;

  public function __construct(
    NewslettersRepository $newsletterRepository,
    NewsletterUrl $newsletterUrl,
    NewsletterOptionFieldsRepository $newsletterOptionFieldsRepository,
    NewsletterOptionsRepository $newsletterOptionsRepository,
    NewsletterSegmentRepository $newsletterSegmentRepository,
    EntityManager $entityManager,
    ShareVisibility $shareVisibility
  ) {
    $this->newsletterRepository = $newsletterRepository;
    $this->newsletterUrl = $newsletterUrl;
    $this->newsletterOptionFieldsRepository = $newsletterOptionFieldsRepository;
    $this->newsletterOptionsRepository = $newsletterOptionsRepository;
    $this->newsletterSegmentRepository = $newsletterSegmentRepository;
    $this->entityManager = $entityManager;
    $this->shareVisibility = $shareVisibility;
  }

  /**
   * @param array $postEmailData - WP_Post data
   * @return array - MailPoet specific email data that will be attached to the post API response
   */
  public function getEmailData($postEmailData): array {
    $newsletter = $this->newsletterRepository->findOneBy(['wpPost' => $postEmailData['id']]);
    $isAutomationNewsletter = $newsletter && ($newsletter->isAutomation() || $newsletter->isAutomationTransactional());
    $showInArchive = $newsletter
      ? $newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_EXCLUDE_FROM_ARCHIVE) !== '1'
      : true;
    return [
      'id' => $newsletter ? $newsletter->getId() : null,
      'subject' => $newsletter ? $newsletter->getSubject() : '',
      'preheader' => $newsletter ? $newsletter->getPreheader() : '',
      'preview_url' => $this->newsletterUrl->getViewInBrowserUrl($newsletter),
      'deleted_at' => $newsletter && $newsletter->getDeletedAt() !== null ? $newsletter->getDeletedAt()->format('c') : null,
      'scheduled_at' => $newsletter ? $newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_SCHEDULED_AT) : null,
      'utm_campaign' => $newsletter ? $newsletter->getGaCampaign() : '',
      'segment_ids' => $newsletter ? $newsletter->getSegmentIds() : [],
      'is_automation_newsletter' => $isAutomationNewsletter,
      'share_url' => $newsletter && $this->shareVisibility->isSupported($newsletter)
        ? $this->newsletterUrl->getPublicShareUrl($newsletter)
        : '',
      'share_visibility' => $newsletter
        ? $this->shareVisibility->getConfiguredVisibility($newsletter)
        : ShareVisibility::VISIBILITY_DEFAULT,
      'effective_share_visibility' => $newsletter
        ? $this->shareVisibility->getEffectiveVisibility($newsletter)
        : $this->shareVisibility->getDefaultVisibility(),
      'can_share' => $newsletter ? $this->shareVisibility->canShare($newsletter) : false,
      'show_in_archive' => $showInArchive,
    ];
  }

  /**
   * Update MailPoet specific data we store with Emails.
   */
  public function saveEmailData(array $data, \WP_Post $emailPost): void {
    $newsletter = $this->newsletterRepository->findOneById($data['id']);
    if (!$newsletter) {
      throw new NotFoundException('Newsletter was not found');
    }
    if ($newsletter->getWpPostId() !== $emailPost->ID) {
      throw new UnexpectedValueException('Newsletter ID does not match the post ID');
    }

    $newsletter->setSubject($data['subject']);
    $newsletter->setPreheader($data['preheader']);

    if (isset($data['utm_campaign'])) {
      $newsletter->setGaCampaign($data['utm_campaign']);
    }

    if (isset($data['deleted_at'])) {
      if (empty($data['deleted_at'])) {
        $data['deleted_at'] = null;
      } else {
        $data['deleted_at'] = new \DateTime($data['deleted_at']);
      }
      $newsletter->setDeletedAt($data['deleted_at']);
    }

    if (array_key_exists('scheduled_at', $data)) {
      $this->updateScheduledAtOption($newsletter, $data['scheduled_at']);
    }

    if (array_key_exists('share_visibility', $data)) {
      $this->updateOption(
        $newsletter,
        NewsletterOptionFieldEntity::NAME_SHARE_VISIBILITY,
        $this->shareVisibility->sanitize((string)$data['share_visibility'])
      );
    }

    if (array_key_exists('show_in_archive', $data)) {
      $this->updateShowInArchiveOption($newsletter, $data['show_in_archive']);
    }

    if (isset($data['segment_ids']) && is_array($data['segment_ids'])) {
      $this->updateSegments($newsletter, $data['segment_ids']);
      $this->entityManager->refresh($newsletter);
    }

    $this->newsletterRepository->flush();
  }

  private function updateShowInArchiveOption(NewsletterEntity $newsletter, $showInArchiveValue): void {
    if (!is_bool($showInArchiveValue)) {
      throw new UnexpectedValueException('Invalid show_in_archive value. Expected a boolean.');
    }

    if ($newsletter->getType() !== NewsletterEntity::TYPE_STANDARD) {
      return;
    }

    $this->updateOption(
      $newsletter,
      NewsletterOptionFieldEntity::NAME_EXCLUDE_FROM_ARCHIVE,
      $showInArchiveValue ? '0' : '1'
    );
  }

  private function updateOption($newsletter, string $optionName, $optionValue): void {
    $optionField = $this->newsletterOptionFieldsRepository->findOneBy([
      'name' => $optionName,
      'newsletterType' => $newsletter->getType(),
    ]);

    if (!$optionField) {
      return;
    }

    $option = $this->newsletterOptionsRepository->findOneBy([
      'newsletter' => $newsletter,
      'optionField' => $optionField,
    ]);

    if (!$option) {
      $option = new NewsletterOptionEntity($newsletter, $optionField);
      $this->newsletterOptionsRepository->persist($option);
      $newsletter->getOptions()->add($option);
    }

    $option->setValue($optionValue);
  }

  private function updateScheduledAtOption($newsletter, $scheduledAtValue): void {
    // Validate the scheduled_at value
    if ($scheduledAtValue !== null && $scheduledAtValue !== '') {
      try {
        new \DateTime($scheduledAtValue);
      } catch (\Exception $e) {
        throw new UnexpectedValueException('Invalid scheduled_at format. Expected a valid datetime string.');
      }
    }

    $this->updateOption($newsletter, NewsletterOptionFieldEntity::NAME_SCHEDULED_AT, $scheduledAtValue);

    // Also update the isScheduled option
    $this->updateOption(
      $newsletter,
      NewsletterOptionFieldEntity::NAME_IS_SCHEDULED,
      $scheduledAtValue !== null && $scheduledAtValue !== '' ? '1' : '0'
    );
  }

  /**
   * @param array $segmentIds Array of segment IDs
   */
  private function updateSegments($newsletter, array $segmentIds): void {
    // Normalize segment IDs to integers for consistent strict comparison
    $segmentIds = array_map('intval', $segmentIds);

    // Remove existing segments that are not in the new list
    $existingSegments = $newsletter->getNewsletterSegments();
    foreach ($existingSegments as $newsletterSegment) {
      $segment = $newsletterSegment->getSegment();
      if (!$segment || !in_array($segment->getId(), $segmentIds, true)) {
        $this->entityManager->remove($newsletterSegment);
      }
    }

    // Add new segments
    foreach ($segmentIds as $segmentId) {
      $segmentIdInt = (int)$segmentId;
      $segment = $this->entityManager->getReference(SegmentEntity::class, $segmentIdInt);
      if (!$segment) {
        continue;
      }

      // Check if the newsletter-segment relationship already exists
      $existingRelation = $this->newsletterSegmentRepository->findOneBy([
        'newsletter' => $newsletter,
        'segment' => $segment,
      ]);

      if (!$existingRelation) {
        $newsletterSegment = new NewsletterSegmentEntity($newsletter, $segment);
        $this->entityManager->persist($newsletterSegment);
      }
    }
    $this->entityManager->flush();
  }

  public function trashEmail(\WP_Post $wpPost) {
    $newsletter = $this->newsletterRepository->findOneBy(['wpPost' => $wpPost->ID]);
    if (!$newsletter) {
      throw new NotFoundException('Newsletter was not found');
    }
    if ($newsletter->getWpPostId() !== $wpPost->ID) {
      throw new UnexpectedValueException('Newsletter ID does not match the post ID');
    }
    $this->newsletterRepository->bulkTrash([$newsletter->getId()]);
  }

  public function getEmailDataSchema(): array {
    return Builder::object([
      'id' => Builder::integer()->nullable(),
      'subject' => Builder::string(),
      'preheader' => Builder::string(),
      'preview_url' => Builder::string(),
      'deleted_at' => Builder::string()->nullable(),
      'scheduled_at' => Builder::string()->nullable(),
      'utm_campaign' => Builder::string(),
      'segment_ids' => Builder::array(),
      'is_automation_newsletter' => Builder::boolean(),
      'share_url' => Builder::string(),
      'share_visibility' => Builder::string(),
      'effective_share_visibility' => Builder::string(),
      'can_share' => Builder::boolean(),
      'show_in_archive' => Builder::boolean(),
    ])->toArray();
  }
}