<?php

declare(strict_types=1);

namespace Inpsyde\MultilingualPress\Module\Comments\TranslationUi\Ajax;

use Inpsyde\MultilingualPress\Framework\Database\Exception\NonexistentTable;
use Inpsyde\MultilingualPress\Framework\Http\RequestHandler;
use Inpsyde\MultilingualPress\Framework\Http\ServerRequest;
use Inpsyde\MultilingualPress\Module\Comments\RelationshipContext\CommentsRelationshipContextFactoryInterface;
use Inpsyde\MultilingualPress\Module\Comments\RelationshipContext\CommentsRelationshipContextInterface;
use RuntimeException;

use function Inpsyde\MultilingualPress\siteExists;
use function Inpsyde\MultilingualPress\translationIds;

class AjaxSearchCommentRequestHandler implements RequestHandler
{
    public const ACTION = 'multilingualpress_remote_comment_search';
    public const FILTER_REMOTE_ARGUMENTS = 'multilingualpress.remote_post_search_arguments';

    /**
     * @var CommentsRelationshipContextFactoryInterface
     */
    protected $relationshipContextFactory;

    /**
     * @var string
     */
    protected $alreadyConnectedNotice;

    public function __construct(CommentsRelationshipContextFactoryInterface $relationshipContextFactory, string $alreadyConnectedNotice)
    {
        $this->relationshipContextFactory = $relationshipContextFactory;
        $this->alreadyConnectedNotice = $alreadyConnectedNotice;
    }

    /**
     * @inheritDoc
     */
    public function handle(ServerRequest $request)
    {
        if (!wp_doing_ajax()) {
            return;
        }

        if (!doing_action('wp_ajax_' . self::ACTION)) {
            wp_send_json_error('Invalid action.');
        }

        $searchQuery = (string)$request->bodyValue(
            'search',
            INPUT_POST,
            FILTER_SANITIZE_SPECIAL_CHARS,
            FILTER_FLAG_NO_ENCODE_QUOTES
        );

        if (!$searchQuery) {
            wp_send_json_error('Missing data.');
        }

        try {
            $context = $this->createContextFromRequest($request);
            wp_send_json_success($this->findComments($context, $searchQuery));
        } catch (RuntimeException $exception) {
            wp_send_json_error($exception->getMessage());
        }
    }

    /**
     * Finds the comment for given context
     *
     * @param CommentsRelationshipContextInterface $context
     * @param string $searchQuery
     * @return array
     */
    protected function findComments(CommentsRelationshipContextInterface $context, string $searchQuery): array
    {

        $args = [
            'post_id' => $context->remotePostId(),
            'fields' => 'ids',
            'search' => $searchQuery,
        ];

        if (is_numeric($searchQuery)) {
            $args['comment__in'] = [(int) $searchQuery];
            // Since we're explicitly searching for a comment ID, we can ignore the 'search' parameter
            unset($args['search']);
        }

        if ($context->hasRemoteComment()) {
            $args['comment__not_in'] = [$context->remoteCommentId()];
        }

        /**
         * Filters the query arguments for the comment search.
         *
         * @param array $args
         */
        $args = (array)apply_filters(self::FILTER_REMOTE_ARGUMENTS, $args);
        $sourceSiteId = get_current_blog_id();

        switch_to_blog($context->remoteSiteId());
        $commentIds = array_map(
            function (int $commentId) use ($sourceSiteId): array {
                $title = $this->isConnectedWithCommentOfSite($commentId, $sourceSiteId)
                    ? "($this->alreadyConnectedNotice)"
                    : "";

                return ['id' => $commentId, 'title' => $title];
            },
            get_comments($args)
        );
        restore_current_blog();

        return $commentIds ?? [];
    }

    /**
     * Creates the relationship context from given request.
     *
     * @param ServerRequest $request
     * @return CommentsRelationshipContextInterface
     * @throws RuntimeException if problem creating.
     */
    protected function createContextFromRequest(ServerRequest $request): CommentsRelationshipContextInterface
    {
        $sourceSiteId = (int)$request->bodyValue(
            'source_site_id',
            INPUT_POST,
            FILTER_SANITIZE_NUMBER_INT
        );

        $sourceCommentId = (int)$request->bodyValue(
            'source_comment_id',
            INPUT_POST,
            FILTER_SANITIZE_NUMBER_INT
        );

        $remoteSiteId = (int)$request->bodyValue(
            'remote_site_id',
            INPUT_POST,
            FILTER_SANITIZE_NUMBER_INT
        );

        $remoteCommentId = (int)$request->bodyValue(
            'remote_comment_id',
            INPUT_POST,
            FILTER_SANITIZE_NUMBER_INT
        );

        if (
            !$sourceSiteId
            || !$sourceCommentId
            || !$remoteSiteId
            || !siteExists($sourceSiteId)
            || !siteExists($remoteSiteId)
        ) {
            throw new RuntimeException('Invalid context.');
        }

        return $this->relationshipContextFactory->createCommentsRelationshipContext(
            $sourceSiteId,
            $remoteSiteId,
            $sourceCommentId,
            $remoteCommentId
        );
    }

    /**
     * Checks if the comment with given comment ID is connected to any comment from given site ID.
     *
     * @param int $commentId The comment ID.
     * @param int $siteId The site ID.
     * @return bool true if is connected, otherwise false.
     * @throws NonexistentTable
     */
    protected function isConnectedWithCommentOfSite(int $commentId, int $siteId): bool
    {
        $translations = translationIds($commentId, 'comment');
        return in_array($commentId, $translations, true) && $translations[$siteId];
    }
}
