<?php

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

use PublishPress\Future\Core\HookableInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\InputValidatorsInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\StepProcessorInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\TriggerRunnerInterface;
use PublishPress\Future\Modules\Workflows\Interfaces\ExecutionContextInterface;
use PublishPress\Future\Framework\Logger\LoggerInterface;
use PublishPress\Future\Modules\Workflows\Domain\Engine\VariableResolvers\ArrayResolver;
use PublishPress\Future\Modules\Workflows\Domain\Engine\VariableResolvers\UserResolver;
use PublishPress\Future\Modules\Workflows\Domain\Steps\Triggers\Definitions\OnUserRoleChange;
use PublishPress\Future\Modules\Workflows\Interfaces\WorkflowExecutionSafeguardInterface;
use PublishPress\FuturePro\Core\HooksAbstract;
use PublishPress\FuturePro\Modules\Workflows\HooksAbstract as WorkflowsHooksAbstract;

class OnUserRoleChangeRunner implements TriggerRunnerInterface
{
    private const META_KEY_CAPABILITIES = 'wp_capabilities';

    /**
     * @var HookableInterface
     */
    private $hooks;

    /**
     * @var array
     */
    private $step;

    /**
     * @var StepProcessorInterface
     */
    private $stepProcessor;

    /**
     * @var InputValidatorsInterface
     */
    private $userQueryValidator;

    /**
     * @var int
     */
    private $workflowId;

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

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

    private $userRolesCache = [];

    /**
     * @var WorkflowExecutionSafeguardInterface
     */
    private $executionSafeguard;

    public function __construct(
        HookableInterface $hooks,
        StepProcessorInterface $stepProcessor,
        InputValidatorsInterface $userQueryValidator,
        ExecutionContextInterface $executionContext,
        LoggerInterface $logger,
        WorkflowExecutionSafeguardInterface $executionSafeguard
    ) {
        $this->hooks = $hooks;
        $this->stepProcessor = $stepProcessor;
        $this->userQueryValidator = $userQueryValidator;
        $this->executionContext = $executionContext;
        $this->logger = $logger;
        $this->executionSafeguard = $executionSafeguard;
    }

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

    public function setup(int $workflowId, array $step): void
    {
        $this->step = $step;
        $this->workflowId = $workflowId;

        $this->hooks->addAction(HooksAbstract::ACTION_UPDATE_USER_META, [$this, 'onUpdateUserMeta'], 10, 3);
        $this->hooks->addAction(HooksAbstract::ACTION_UPDATED_USER_META, [$this, 'onUpdatedUserMeta'], 10, 4);
    }

    public function onUpdateUserMeta(int $metaId, int $userId, string $metaKey)
    {
        if ($metaKey !== self::META_KEY_CAPABILITIES) {
            return;
        }

        $currentRoles = get_user_meta($userId, $metaKey, true);

        $this->userRolesCache[$userId] = $currentRoles;
    }

    public function onUpdatedUserMeta(int $metaId, int $userId, string $metaKey, $metaValue)
    {
        if ($metaKey !== self::META_KEY_CAPABILITIES) {
            return;
        }

        $currentRoles = $this->userRolesCache[$userId] ?? null;

        if ($currentRoles === $metaValue) {
            return;
        }

        $newRoles = array_map('trim', array_keys(array_filter($metaValue)));
        $currentRoles = array_map('trim', array_keys(array_filter($currentRoles)));


        $addedRoles = array_diff($newRoles, $currentRoles);
        $removedRoles = array_diff($currentRoles, $newRoles);

        if (empty($addedRoles) && empty($removedRoles)) {
            return;
        }

        $this->triggerCallback($userId, $newRoles, $addedRoles, $removedRoles, $currentRoles);
    }

    private function triggerCallback(
        int $userId,
        array $userRoles,
        array $addedRoles,
        array $removedRoles,
        array $currentRoles
    ) {
        $nodeSlug = $this->stepProcessor->getSlugFromStep($this->step);

        if ($this->shouldAbortExecution($nodeSlug)) {
            return;
        }

        $this->stepProcessor->executeSafelyWithErrorHandling(
            $this->step,
            function ($step, $userId, $userRoles, $addedRoles, $removedRoles, $currentRoles) {
                $nodeSlug = $this->stepProcessor->getSlugFromStep($step);

                $userQueryArgs = [
                    'userId' => $userId,
                    'roles' => $currentRoles,
                    'node' => $step['node'],
                ];

                if (! $this->userQueryValidator->validate($userQueryArgs)) {
                    return false;
                }

                $this->executionContext->setVariable($nodeSlug, [
                    'user' => new UserResolver($userId),
                    'addedRoles' => new ArrayResolver($addedRoles),
                    'removedRoles' => new ArrayResolver($removedRoles),
                ]);

                $this->stepProcessor->triggerCallbackIsRunning();

                $this->logger->debug(
                    $this->stepProcessor->prepareLogMessage(
                        'Trigger is running | Slug: %s | User ID: %d',
                        $nodeSlug,
                        $userId
                    )
                );

                $this->hooks->doAction(
                    WorkflowsHooksAbstract::ACTION_WORKFLOW_TRIGGER_EXECUTED,
                    $this->workflowId,
                    $this->step
                );

                $this->stepProcessor->runNextSteps($step);
            },
            $userId,
            $userRoles,
            $addedRoles,
            $removedRoles,
            $currentRoles
        );
    }

    private function shouldAbortExecution(string $stepSlug): bool
    {
        if (
            $this->executionSafeguard->detectInfiniteLoop(
                $this->executionContext,
                $this->step
            )
        ) {
            $this->logger->debug(
                $this->stepProcessor->prepareLogMessage(
                    'Infinite loop detected for step %s, skipping',
                    $stepSlug
                )
            );

            return true;
        }

        return false;
    }
}
