<?php declare(strict_types = 1);

namespace MailPoet\Automation\Integrations\MailPoet\Templates;

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


use MailPoet\Automation\Engine\Data\Automation;
use MailPoet\Automation\Engine\Exceptions\NotFoundException;
use MailPoet\Automation\Engine\WordPress;
use MailPoet\Config\Env;
use MailPoet\EmailEditor\Integrations\MailPoet\EmailEditor;
use MailPoet\EmailEditor\Integrations\MailPoet\Patterns\PatternsController;
use MailPoet\EmailEditor\Integrations\MailPoet\Templates\TemplatesController;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\NewsletterOptionEntity;
use MailPoet\Entities\NewsletterOptionFieldEntity;
use MailPoet\Entities\WpPostEntity;
use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\Options\NewsletterOptionFieldsRepository;
use MailPoet\Newsletter\Options\NewsletterOptionsRepository;
use MailPoet\Settings\SettingsController;
use MailPoet\Util\Security;
use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\Doctrine\ORM\EntityManager;

class EmailFactory {
  /** @var NewslettersRepository */
  private $newslettersRepository;

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

  /** @var string|null */
  protected $templatesDirectory;

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

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

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

  private PatternsController $patternsController;

  private TemplatesController $templatesController;

  private EntityManager $entityManager;

  private WPFunctions $wpFunctions;

  public function __construct(
    NewslettersRepository $newslettersRepository,
    SettingsController $settings,
    WordPress $wp,
    NewsletterOptionsRepository $newsletterOptionsRepository,
    NewsletterOptionFieldsRepository $newsletterOptionFieldsRepository,
    PatternsController $patternsController,
    TemplatesController $templatesController,
    WPFunctions $wpFunctions,
    EntityManager $entityManager
  ) {
    $this->newslettersRepository = $newslettersRepository;
    $this->settings = $settings;
    $this->wp = $wp;
    $this->newsletterOptionsRepository = $newsletterOptionsRepository;
    $this->newsletterOptionFieldsRepository = $newsletterOptionFieldsRepository;
    $this->patternsController = $patternsController;
    $this->templatesController = $templatesController;
    $this->entityManager = $entityManager;
    $this->wpFunctions = $wpFunctions;
  }

  /**
   * Create an email from a template and store it in the database
   *
   * @param array $data Email data including subject, preheader, etc.
   * @return int|null The ID of the created email or null if the email couldn't be created
   */
  public function createEmail(array $data = []): ?int {
    $newsletter = $this->createNewsletterEntity($data);

    // Set content if provided
    if (isset($data['content'])) {
      $newsletter->setBody($data['content']);
    } elseif (isset($data['template'])) {
      $template = $this->loadTemplate($data['template']);
      if ($template) {
        $newsletter->setBody($template);
      }
    }

    // Save the newsletter to the database
    $this->newslettersRepository->persist($newsletter);
    $this->newslettersRepository->flush();

    // Return the newsletter ID
    return $newsletter->getId();
  }

  /**
   * Create a block editor email from a pattern and store it in the database.
   *
   * This method creates a WordPress post with the pattern content and links it
   * to a NewsletterEntity for use with the block email editor.
   *
   * @param array $data Email data including:
   *   - 'pattern' (string, required): The pattern name (e.g., 'welcome-email-content')
   *   - 'subject' (string, optional): Email subject
   *   - 'preheader' (string, optional): Email preheader
   *   - 'sender_name' (string, optional): Sender name
   *   - 'sender_address' (string, optional): Sender email address
   * @param string|null $templateSlug The slug of the template to associate with the email.
   * @return array{email_id: int, email_wp_post_id: int}|null The IDs of the created email and WP post, or null if creation failed
   */
  public function createBlockEditorEmail(array $data, ?string $templateSlug = null): ?array {
    $patternName = $data['pattern'] ?? null;
    if (!$patternName) {
      return null;
    }

    // Get pattern content
    $patternContent = $this->patternsController->getPatternContent($patternName);
    if ($patternContent === null) {
      return null;
    }

    // Create a WordPress post with the pattern content
    // The meta_input flag prevents other plugins from creating duplicate newsletters
    // Automation emails use 'private' status to prevent them from appearing in public queries
    $postId = $this->wpFunctions->wpInsertPost([
      'post_content' => $patternContent,
      'post_type' => EmailEditor::MAILPOET_EMAIL_POST_TYPE,
      'post_status' => 'private',
      'post_author' => $this->wpFunctions->getCurrentUserId(),
      'post_title' => $data['subject'] ?? __('New Email', 'mailpoet'),
      'meta_input' => ['_mailpoet_is_automation_email' => '1'],
    ]);

    if (!is_int($postId) || $postId <= 0) {
      return null;
    }

    // Set the email template to associate the template wrapper (header, footer, unsubscribe links)
    $templateSlug = $templateSlug ?: $this->templatesController->getDefaultTemplateSlug();
    $this->wpFunctions->updatePostMeta($postId, '_wp_page_template', $templateSlug);

    // Create a new newsletter entity
    try {
      $wpPost = $this->entityManager->getReference(WpPostEntity::class, $postId);
      $newsletter = $this->createNewsletterEntity($data, $wpPost);

      $this->newslettersRepository->persist($newsletter);
      $this->newslettersRepository->flush();
    } catch (\Throwable $e) {
      $this->wpFunctions->wpDeletePost($postId, true);
      throw $e;
    }

    $emailId = $newsletter->getId();
    if ($emailId === null) {
      $this->wpFunctions->wpDeletePost($postId, true);
      return null;
    }

    return [
      'email_id' => $emailId,
      'email_wp_post_id' => $postId,
    ];
  }

  /**
   * Sets automation and step ID for all email steps in an automation
   *
   * @param Automation $automation The automation object
   * @return void
   */
  public function setAutomationIdForEmails(Automation $automation): void {
    // Skip if automation ID is not set
    try {
      $automationId = $automation->getId();
    } catch (\Exception $e) {
      return;
    }

    $steps = $automation->getSteps();
    $emailSteps = array_filter($steps, function($step) {
      return $step->getKey() === 'mailpoet:send-email';
    });

    foreach ($emailSteps as $step) {
      $args = $step->getArgs();
      $emailId = $args['email_id'] ?? null;

      if ($emailId) {
        $newsletter = $this->newslettersRepository->findOneById($emailId);
        if (!$newsletter) {
          continue;
        }

        $existingAutomationId = $newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_AUTOMATION_ID);
        $existingStepId = $newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_AUTOMATION_STEP_ID);

        if ($existingAutomationId === (string)$automationId && $existingStepId === $step->getId()) {
          continue;
        }

        $this->storeNewsletterOption(
          $newsletter,
          NewsletterOptionFieldEntity::NAME_AUTOMATION_ID,
          (string)$automationId
        );

        $this->storeNewsletterOption(
          $newsletter,
          NewsletterOptionFieldEntity::NAME_AUTOMATION_STEP_ID,
          $step->getId()
        );

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

  /**
   * Stores a newsletter option
   *
   * @param NewsletterEntity $newsletter The newsletter entity
   * @param string $optionName The name of the option
   * @param string|null $optionValue The value of the option
   * @return void
   */
  private function storeNewsletterOption(NewsletterEntity $newsletter, string $optionName, ?string $optionValue = null): void {
    if (!$optionValue || !$this->newsletterOptionsRepository || !$this->newsletterOptionFieldsRepository) {
      return;
    }

    $existingOption = $newsletter->getOption($optionName);
    if ($existingOption && $existingOption->getValue() === $optionValue) {
      return; // Skip if option already exists with the same value
    }

    $field = $this->newsletterOptionFieldsRepository->findOneBy([
      'name' => $optionName,
      'newsletterType' => $newsletter->getType(),
    ]);

    if (!$field) {
      return;
    }

    // If option exists but with different value, update it
    if ($existingOption) {
      $existingOption->setValue($optionValue);
      return;
    }

    // Otherwise create a new option
    $option = new NewsletterOptionEntity($newsletter, $field);
    $option->setValue($optionValue);
    $this->newsletterOptionsRepository->persist($option);
    $newsletter->getOptions()->add($option);
  }

  /**
   * Create a NewsletterEntity with common automation email properties.
   *
   * @param array $data Email data including subject, preheader, sender_name, sender_address
   * @param WpPostEntity|null $wpPost Optional WP post to associate with the newsletter
   * @return NewsletterEntity The created newsletter entity (not persisted)
   */
  private function createNewsletterEntity(array $data, ?WpPostEntity $wpPost = null): NewsletterEntity {
    $newsletter = new NewsletterEntity();
    $newsletter->setType(NewsletterEntity::TYPE_AUTOMATION);
    $newsletter->setStatus(NewsletterEntity::STATUS_ACTIVE);
    $newsletter->setSubject($data['subject'] ?? '');
    $newsletter->setPreheader($data['preheader'] ?? '');
    $newsletter->setSenderName($data['sender_name'] ?? $this->getDefaultSenderName());
    $newsletter->setSenderAddress($data['sender_address'] ?? $this->getDefaultSenderAddress());
    $newsletter->setHash(Security::generateHash());

    if ($wpPost !== null) {
      $newsletter->setWpPost($wpPost);
    }

    return $newsletter;
  }

  /**
   * Get the default sender name from settings
   */
  private function getDefaultSenderName(): string {
    return $this->settings->get('sender.name', '');
  }

  /**
   * Get the default sender address from settings
   */
  private function getDefaultSenderAddress(): string {
    return $this->settings->get('sender.address', '');
  }

  /**
   * Load a template from a file
   *
   * @param string $templateName The name of the template file (without .json extension)
   * @return array|null The template body or null if the template doesn't exist
   */
  public function loadTemplate(string $templateName): ?array {
    $templatePath = $this->getTemplatePath($templateName);

    if (!file_exists($templatePath)) {
      throw new NotFoundException('Template not found: ' . $templateName);
    }
    return $this->fetchEmailTemplate($templatePath);
  }

  /**
   * Get the path to a template file
   *
   * @param string $templateName The name of the template file (without .json extension)
   * @return string The full path to the template file
   */
  protected function getTemplatePath(string $templateName): string {
    $sanitizedTemplateName = $this->wp->sanitizeFileName($templateName);
    return $this->getTemplatesDirectory() . '/' . $sanitizedTemplateName . '.json';
  }

  /**
   * Fetch email template from a file
   *
   * @param string $templatePath The path to the template file
   * @return array|null The template body or null if the template couldn't be loaded
   */
  private function fetchEmailTemplate(string $templatePath): ?array {
    $templateString = file_get_contents($templatePath);
    if ($templateString === false) {
      return null;
    }

    $templateArr = json_decode((string)$templateString, true);
    if (!is_array($templateArr) || !isset($templateArr['body'])) {
      return null;
    }

    return $templateArr['body'];
  }

  /**
   * Set the templates directory
   *
   * @param string|null $directory The directory where templates are stored
   * @return self
   */
  public function setTemplatesDirectory(?string $directory): self {
    $this->templatesDirectory = $directory;
    return $this;
  }

  /**
   * Get the templates directory
   *
   * @return string The directory where templates are stored
   */
  public function getTemplatesDirectory(): string {
    return $this->templatesDirectory
      ?: Env::$libPath . '/Automation/Integrations/MailPoet/Templates/EmailTemplates';
  }
}
