<?php

namespace PublishPress\FuturePro\Modules\Workflows\Domain\Steps\Actions\Runners;

use Exception;
use PublishPress\Future\Framework\Logger\LoggerInterface;
use PublishPress\Future\Modules\Workflows\Domain\Steps\Actions\Definitions\QueryPosts;
use PublishPress\Future\Modules\Workflows\Interfaces\StepRunnerInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\StepProcessorInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\ExecutionContextInterface;
use PublishPress\FuturePro\Modules\Workflows\Interfaces\JsonLogicPreprocessorInterface;

class QueryPostsRunner implements StepRunnerInterface
{
    /**
     * @var StepProcessorInterface
     */
    private $stepProcessor;

    /**
     * @var ExecutionContextInterface
     */
    private $executionContext;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var JsonLogicPreprocessorInterface
     */
    private $jsonLogicSqlPreprocessor;

    public function __construct(
        StepProcessorInterface $stepProcessor,
        ExecutionContextInterface $executionContext,
        LoggerInterface $logger,
        JsonLogicPreprocessorInterface $jsonLogicSqlPreprocessor
    ) {
        $this->stepProcessor = $stepProcessor;
        $this->executionContext = $executionContext;
        $this->logger = $logger;
        $this->jsonLogicSqlPreprocessor = $jsonLogicSqlPreprocessor;
    }

    public static function getNodeTypeName(): string
    {
        return QueryPosts::getNodeTypeName();
    }

    public function setup(array $step): void
    {
        $this->stepProcessor->setup($step, [$this, 'setupCallback']);
    }

    public function setupCallback(array $step): void
    {
        $this->stepProcessor->executeSafelyWithErrorHandling(
            $step,
            function ($step) {
                $node = $this->stepProcessor->getNodeFromStep($step);
                $nodeSettings = $this->stepProcessor->getNodeSettings($node);
                $nodeSlug = $this->stepProcessor->getSlugFromStep($step);

                if (empty($nodeSettings)) {
                    throw new Exception('Node has empty settings');
                }

                $posts = $this->getPostsFromQuery($nodeSettings);

                // No post found
                if (empty($posts)) {
                    $this->logger->debug(
                        $this->stepProcessor->prepareLogMessage(
                            'No posts found, skipping | Slug: %1$s',
                            $nodeSlug
                        )
                    );

                    return;
                }

                $foundPosts = array_map('intval', $posts);

                $this->logger->debug(
                    $this->stepProcessor->prepareLogMessage(
                        'Found %1$s posts | Slug: %2$s',
                        count($foundPosts),
                        $nodeSlug
                    )
                );

                $this->executionContext->setVariable($nodeSlug . '.posts', $foundPosts);
            },
            $step
        );
    }

    private function isLegacyPostQuery(array $nodeSettings)
    {
        return ! isset($nodeSettings['postQuery']['json']) || empty($nodeSettings['postQuery']['json']);
    }

    private function getPostsFromLegacyQuery(array $nodeSettings)
    {
        // Post Type
        $postType = $nodeSettings['postQuery']['postType'] ?? false;
        $postQuery = [
            'posts_per_page' => -1,
            'post_status' => 'any',
            'fields' => 'ids',
        ];
        if (! empty($postType)) {
            $postQuery['post_type'] = $postType;
        }

        // Post ID
        $postId = $nodeSettings['postQuery']['postId'] ?? false;
        if (! empty($postId)) {
            if (! is_array($postId)) {
                $postId = [$postId];
            }

            $postId = array_map('intval', $postId);

            $postQuery['post__in'] = $postId;
        }

        // Post Status
        $postStatus = $nodeSettings['postQuery']['postStatus'] ?? false;
        if (! empty($postStatus)) {
            $postQuery['post_status'] = $postStatus;
        }

        // Post Author
        $postAuthor = $nodeSettings['postQuery']['postAuthor'] ?? false;
        if (! empty($postAuthor)) {
            if (! is_array($postAuthor)) {
                $postAuthor = [$postAuthor];
            }

            $postQuery['author__in'] = $this->executionContext->resolveExpressionsInArray($postAuthor);
        }

        // Post Terms
        $postTerms = $nodeSettings['postQuery']['postTerms'] ?? false;
        if (! empty($postTerms)) {
            if (! isset($postQuery['tax_query'])) {
                $postQuery['tax_query'] = []; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
            }

            $groupedTerms = [];
            foreach ($postTerms as $term) {
                $termParts = explode(':', $term);
                $taxonomy = $termParts[0];
                $termId = $termParts[1];

                if (! isset($groupedTerms[$taxonomy])) {
                    $groupedTerms[$taxonomy] = [];
                }

                $groupedTerms[$taxonomy][] = $termId;
            }

            foreach ($groupedTerms as $taxonomy => $terms) {
                $postQuery['tax_query'][] = [
                    'taxonomy' => $taxonomy,
                    'terms' => $terms,
                    'field' => 'term_id',
                ];
            }
        }

        return get_posts($postQuery);
    }

    private function getPostsFromJsonQuery(array $nodeSettings)
    {
        global $wpdb;
        $postQuery = $nodeSettings['postQuery']['json'];

        $processedQuery = $this->jsonLogicSqlPreprocessor->process($postQuery);

        $sql = "SELECT DISTINCT p.* FROM {$wpdb->posts} p WHERE ";
        $sql .= $processedQuery['sql'];


        // Execute the query
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
        return $wpdb->get_results(
            $wpdb->prepare($sql, $processedQuery['parameters']) // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
        );
    }

    private function getPostsFromQuery(array $nodeSettings)
    {
        if ($this->isLegacyPostQuery($nodeSettings)) {
            return $this->getPostsFromLegacyQuery($nodeSettings);
        }

        return $this->getPostsFromJsonQuery($nodeSettings);
    }
}
