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

namespace MailPoet\Newsletter;

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


use MailPoet\Logging\LoggerFactory;
use MailPoet\Newsletter\Editor\Transformer;
use MailPoet\WooCommerce\Helper as WCHelper;
use MailPoet\WP\Functions as WPFunctions;

class DynamicProducts {

  /** @var LoggerFactory */
  private $loggerFactory;

  /** @var int|false */
  private $newsletterId;

  /** @var NewsletterPostsRepository */
  private $newsletterPostsRepository;

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

  /** @var WCHelper */
  private $wcHelper;

  public function __construct(
    LoggerFactory $loggerFactory,
    NewsletterPostsRepository $newsletterPostsRepository,
    WPFunctions $wp,
    WCHelper $wcHelper
  ) {
    $this->loggerFactory = $loggerFactory;
    $this->newsletterPostsRepository = $newsletterPostsRepository;
    $this->wp = $wp;
    $this->wcHelper = $wcHelper;
  }

  public function filterOutSentPosts(string $where): string {
    $newsletterPostsTableName = $this->newsletterPostsRepository->getTableName();
    $sentPostsQuery = 'SELECT ' . $newsletterPostsTableName . '.post_id FROM '
      . $newsletterPostsTableName . ' WHERE '
      . $newsletterPostsTableName . ".newsletter_id='" . $this->newsletterId . "'";

    $wherePostUnsent = 'ID NOT IN (' . $sentPostsQuery . ')';

    if (!empty($where)) $wherePostUnsent = ' AND ' . $wherePostUnsent;

    return $where . $wherePostUnsent;
  }

  public function ensureConsistentQueryType(\WP_Query $query) {
    // Queries with taxonomies are autodetected as 'is_archive=true' and 'is_home=false'
    // while queries without them end up being 'is_archive=false' and 'is_home=true'.
    // This is to fix that by always enforcing constistent behavior.
    $query->is_archive = true; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
    $query->is_home = false; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
  }

  public function getPosts(BlockPostQuery $query) {
    $this->newsletterId = $query->newsletterId;
    // Get posts as logged out user, so private posts hidden by other plugins (e.g. UAM) are also excluded
    $currentUserId = $this->wp->getCurrentUserId();
    // phpcs:ignore Generic.PHP.ForbiddenFunctions.Discouraged
    wp_set_current_user(0);

    $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
      'loading dynamic products',
      [
        'args' => $query->args,
        'posts_to_exclude' => $query->postsToExclude,
        'newsletter_id' => $query->newsletterId,
        'newer_than_timestamp' => $query->newerThanTimestamp,
        'include_product_ids' => $query->includeProductIds,
      ]
    );

    // set low priority to execute 'ensureConstistentQueryType' before any other filter
    $filterPriority = defined('PHP_INT_MIN') ? constant('PHP_INT_MIN') : ~PHP_INT_MAX;
    $this->wp->addAction('pre_get_posts', [$this, 'ensureConsistentQueryType'], $filterPriority);
    $this->_attachSentPostsFilter($query->newsletterId);
    $parameters = $query->getQueryParams();
    $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
      'getting dynamic products',
      ['parameters' => $parameters]
    );

    // Convert WP_Query parameters to WC_Product_Query parameters
    $wcArgs = [
      'limit' => $parameters['posts_per_page'] ?? -1,
      'orderby' => $parameters['orderby'] ?? 'date',
      'order' => $parameters['order'] ?? 'DESC',
      'exclude' => $query->postsToExclude, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
    ];

    // Exclude out-of-stock and products that are not on backorder
    $excludeOutOfStock = $query->args['excludeOutOfStock'] ?? false;
    if ($excludeOutOfStock === true || $excludeOutOfStock === 'true') {
      $wcArgs['stock_status'] = $this->wp->applyFilters('mailpoet_products_exclude_out_of_stock_stock_status', ['instock', 'lowstock', 'onbackorder']);
    }

    // If we have specific product IDs to include, use them
    if (!empty($parameters['post__in'])) {
      $wcArgs['include'] = $parameters['post__in'];
    }

    // WooCommerce Product Query does not support 'any' status,
    // so we need to handle it manually
    $postStatus = $parameters['post_status'] ?? 'publish';
    // Default to published products only
    $wcArgs['status'] = 'publish';

    // Store original user capabilities for later permission filtering
    $canEditPrivate = $this->wp->userCan($currentUserId, 'edit_private_posts');
    $canEditOthers = $this->wp->userCan($currentUserId, 'edit_others_posts');
    $isAdmin = $this->wp->userCan($currentUserId, 'manage_options');

    if (!empty($parameters['tax_query'])) {
      $wcArgs['tax_query'] = $parameters['tax_query'];
    }

    if (!empty($parameters['date_query'])) {
      $wcArgs['date_query'] = $parameters['date_query'];
    }

    // Fetch published products (safe for everyone)
    $products = $this->wcHelper->wcGetProducts($wcArgs);

    // For privileged users, fetch additional product statuses separately and merge
    if ($postStatus === 'any' && ($canEditPrivate || $isAdmin)) {
      // Editors get private products
      if ($canEditPrivate) {
        $privateArgs = $wcArgs;
        $privateArgs['status'] = 'private';
        $privateProducts = $this->wcHelper->wcGetProducts($privateArgs);
        $products = array_merge($products, $privateProducts);
      }

      // Admins get draft and pending products too
      if ($isAdmin) {
        $draftArgs = $wcArgs;
        $draftArgs['status'] = ['draft', 'pending'];
        $draftProducts = $this->wcHelper->wcGetProducts($draftArgs);
        $products = array_merge($products, $draftProducts);
      }
    }

    $this->logPosts($products);

    $this->wp->removeAction('pre_get_posts', [$this, 'ensureConsistentQueryType'], $filterPriority);
    $this->_detachSentPostsFilter($query->newsletterId);
    // phpcs:ignore Generic.PHP.ForbiddenFunctions.Discouraged
    wp_set_current_user($currentUserId);
    return $products;
  }

  public function transformPosts($args, $posts) {
    $transformer = new Transformer($args);
    return $transformer->transform($posts);
  }

  private function _attachSentPostsFilter($newsletterId) {
    if ($newsletterId > 0) {
      $this->wp->addAction('posts_where', [$this, 'filterOutSentPosts']);
    }
  }

  private function _detachSentPostsFilter($newsletterId) {
    if ($newsletterId > 0) {
      $this->wp->removeAction('posts_where', [$this, 'filterOutSentPosts']);
    }
  }

  private function logPosts(array $posts) {
    $postsToLog = [];
    foreach ($posts as $post) {
      $postsToLog[] = [
        'id' => $post->get_id(),
        'post_date' => $post->get_date_created()->format('Y-m-d H:i:s'), // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
      ];
    }
    $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
      'dynamic products loaded posts',
      ['posts' => $postsToLog]
    );
  }
}
