<?php

namespace PublishPress\FuturePro\Modules\Workflows\Domain\Steps\Processors;

use PublishPress\Future\Framework\WordPress\Facade\HooksFacade;
use PublishPress\Future\Modules\Workflows\Interfaces\StepProcessorInterface;
use PublishPress\Future\Framework\Logger\LoggerInterface;
use PublishPress\Future\Modules\Workflows\Domain\Steps\Actions\Definitions\SendInSiteNotification;
use PublishPress\Future\Modules\Workflows\Interfaces\ExecutionContextInterface;
use PublishPress\Future\Modules\Workflows\Models\WorkflowModel;
use PublishPress\FuturePro\Modules\Notifications\Models\NotificationModel;
use PublishPress\FuturePro\Modules\Workflows\Interfaces\InSiteNotificationdProcessorInterface;
use PublishPress\FuturePro\Modules\Workflows\Interfaces\RecipientsModelInterface;
use PublishPress\FuturePro\Modules\Workflows\Models\EventDrivenActionModel;

class InSiteNotification implements InSiteNotificationdProcessorInterface
{
    /**
     * @var HooksFacade
     */
    private $hooks;

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

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

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

    /**
     * @var RecipientsModelInterface
     */
    private $recipientsModel;

    /**
     *
     * @var string
     */
    private $recipientSettingName = 'recipient';

    /**
     * A routing table that maps legacy branches to their corresponding new branches.
     *
     * @var array
     */
    private $branchRouting = [];

    public function __construct(
        HooksFacade $hooks,
        StepProcessorInterface $generalProcessor,
        LoggerInterface $logger,
        ExecutionContextInterface $executionContext,
        RecipientsModelInterface $recipientsModel
    ) {
        $this->hooks = $hooks;
        $this->generalProcessor = $generalProcessor;
        $this->executionContext = $executionContext;
        $this->logger = $logger;
        $this->recipientsModel = $recipientsModel;
    }

    public function setup(array $step, callable $setupCallback): void
    {
        $node = $this->getNodeFromStep($step);
        $nodeSettings = $this->getNodeSettings($node);

        $subject = $this->getSubject($nodeSettings);
        $message = $this->getMessage($nodeSettings);
        $recipients = $this->getRecipients($nodeSettings);
        $recipients = $this->recipientsModel->resolveRecipients($recipients);
        $notificationOptions = $this->getNotificationOptions($nodeSettings);
        $notificationType = $this->getNotificationType($nodeSettings);

        $message = $subject . "\n" . $message;

        if (empty($recipients)) {
            $this->addDebugLogMessage('List of recipients is empty');

            return;
        }

        $workflowId = $this->executionContext->getVariable('global.workflow.id');
        $stepId = $step['node']['id'];

        foreach ($recipients as $recipientId) {
            if (empty($recipientId)) {
                $this->addErrorLogMessage('Recipient ID is empty');

                continue;
            }

            $this->addDebugLogMessage(
                'Sending in-site notification to %1$s | Subject: %2$s',
                $recipientId,
                $subject
            );

            // Prepare the notification
            $notificationModel = new NotificationModel();
            $notificationModel->userId = $recipientId;
            $notificationModel->message = $message;
            $notificationModel->workflowId = $workflowId;
            $notificationModel->stepId = $stepId;
            $notificationModel->type = $notificationType;
            $notificationId = $notificationModel->create(
                [
                    'options' => $notificationOptions,
                ],
                $this->executionContext->getCompactedRuntimeVariables()
            );

            if ($notificationId) {
                $this->addDebugLogMessage('Notification sent to user %1$s', $recipientId);
            } else {
                $this->addErrorLogMessage('Failed to send notification to user %1$s', $recipientId);
            }
        }
    }

    public function setRecipientSettingName(string $recipientSettingName): void
    {
        $this->recipientSettingName = $recipientSettingName;
    }

    public function runNextSteps(array $step, string $branch = 'output'): void
    {
        $branch = $this->branchRouting[$branch] ?? $branch;

        $this->generalProcessor->runNextSteps($step, $branch);
    }

    public function getNextSteps(array $step, string $branch = 'output'): array
    {
        return $this->generalProcessor->getNextSteps($step, $branch);
    }

    public function getNodeFromStep(array $step)
    {
        return $this->generalProcessor->getNodeFromStep($step);
    }

    public function getSlugFromStep(array $step)
    {
        return $this->generalProcessor->getSlugFromStep($step);
    }

    public function getNodeSettings(array $node)
    {
        return $this->generalProcessor->getNodeSettings($node);
    }

    public function logError(string $message, int $workflowId, array $step)
    {
        $this->addErrorLogMessage($message);
    }

    public function triggerCallbackIsRunning(): void
    {
        $this->generalProcessor->triggerCallbackIsRunning();
    }

    public function prepareLogMessage(string $message, ...$args): string
    {
        return $this->generalProcessor->prepareLogMessage($message, ...$args);
    }

    public function executeSafelyWithErrorHandling(array $step, callable $callback, ...$args): void
    {
        $this->generalProcessor->executeSafelyWithErrorHandling($step, $callback, ...$args);
    }

    public function setBranchRouting(array $branchRouting): void
    {
        $this->branchRouting = $branchRouting;
    }

    public function actionCallback(array $compactedArgs, array $originalArgs, bool $triggerCallbackIsRunning = false)
    {
        $notificationId = null;

        if (
            ! isset($compactedArgs['pluginVersion'])
            || version_compare($compactedArgs['pluginVersion'], '4.6.999', '<')
        ) {
            if (! isset($compactedArgs['notificationId'])) {
                $this->addDebugLogMessage('No notification ID found in the compacted arguments');

                return;
            }

            $notificationId = $compactedArgs['notificationId'];
        } else {
            if (! isset($compactedArgs['eventData']['notificationId'])) {
                $this->addDebugLogMessage('No notification ID found in the compacted arguments');

                return;
            }

            $notificationId = (int) $compactedArgs['eventData']['notificationId'];
        }

        if (! isset($compactedArgs['event'])) {
            $this->addDebugLogMessage('No event found in the compacted arguments');

            return;
        }

        $event = $compactedArgs['event'];
        $workflowId = $this->executionContext->getVariable('global.workflow.id');

        if ($triggerCallbackIsRunning) {
            $this->triggerCallbackIsRunning();
        }

        // Check if the workflow is still active
        $workflowModel = new WorkflowModel();
        $workflowModel->load($workflowId);

        $notificationModel = new NotificationModel();
        $notificationExists = $notificationModel->load($notificationId);

        if (! $notificationExists) {
            $this->addDebugLogMessage('Notification not found');

            return;
        }

        if (! $notificationModel->belongsToCurrentUser()) {
            $this->addDebugLogMessage('Notification %1$s is not owned by the current user', $notificationId);

            return;
        }

        $actionId = $notificationModel->actionId;

        if (! $workflowModel->isActive()) {
            // TODO: Log this into the scheduler log
            $this->cancelEventDrivenAction($actionId);

            $this->addDebugLogMessage(
                'Workflow %d is inactive, cancelling scheduled action %s',
                $workflowId,
                $actionId
            );

            return;
        }

        $eventDrivenActionModel = new EventDrivenActionModel();
        $eventDrivenActionModel->load($actionId);

        if (! $eventDrivenActionModel->loaded()) {
            $this->addDebugLogMessage('Event driven action not found');

            return;
        }

        $eventDrivenActionModel->markAsRunning();

        $this->addDebugLogMessage(
            'Event driven action with ID %d is running',
            $actionId
        );

        $step = $workflowModel->getPartialRoutineTreeFromNodeId($compactedArgs['step']['nodeId']);
        $stepSlug = $this->getSlugFromStep($step);

        $node = $this->getNodeFromStep($step);
        $nodeSettings = $this->getNodeSettings($node);
        $recipients = $this->getRecipients($nodeSettings);
        $recipients = $this->recipientsModel->resolveRecipients($recipients);
        $optionName = $compactedArgs['actionArgs']['optionName'] ?? null;

        $this->executionContext->setVariable($stepSlug, [
            'optionName' => $optionName,
            'recipients' => $recipients,
        ]);

        if ($event === 'onNotificationOption' || $event === 'on_read') {
            $branch = $optionName;
            $this->runNextSteps($step, $branch);
        }

        $this->addDebugLogMessage(
            'Event driven action with ID %d has been successfully completed',
            $actionId
        );

        $eventDrivenActionModel->markAsCompleted();
    }

    private function addDebugLogMessage(string $message, ...$args): void
    {
        $this->logger->debug($this->prepareLogMessage($message, ...$args));
    }

    private function addErrorLogMessage(string $message, ...$args): void
    {
        $this->logger->error($this->prepareLogMessage($message, ...$args));
    }

    private function getRecipients(array $nodeSettings): array
    {
        if (isset($nodeSettings[$this->recipientSettingName]['expression'])) {
            $recipient = $nodeSettings[$this->recipientSettingName]['expression'];
        } elseif (isset($nodeSettings[$this->recipientSettingName]['recipient'])) {
            // Legacy settings
            $recipient = $nodeSettings[$this->recipientSettingName]['recipient'] ?? null;
        }

        if (empty($recipient)) {
            $recipient = 'administrator';
        }

        $recipient = $this->executionContext->resolveExpressionsInText($recipient);

        if (! is_array($recipient)) {
            $recipient = explode(',', (string)$recipient);
        }

        $recipient = array_map('trim', $recipient);
        $recipient = array_unique($recipient);

        return $recipient;
    }

    private function getSubject(array $nodeSettings): string
    {
        $subject = $nodeSettings['subject'] ?? '';

        if (is_array($subject) && isset($subject['expression'])) {
            $subject = $subject['expression'];
        }

        if (empty($subject)) {
            $subject = SendInSiteNotification::getDefaultSubject();
        }
        $subject = $this->executionContext->resolveExpressionsInText($subject);

        return sanitize_text_field($subject);
    }

    private function getMessage(array $nodeSettings): string
    {
        $message = $nodeSettings['message'] ?? '';

        if (is_array($message) && isset($message['expression'])) {
            $message = $message['expression'];
        }

        if (empty($message)) {
            $message = SendInSiteNotification::getDefaultMessage();
        }

        $message = $this->executionContext->resolveExpressionsInText($message);

        // TODO: Add support for HTML emails. Block editor or separated email templates?
        return sanitize_textarea_field($message);
    }

    private function cancelEventDrivenAction(int $actionId): void
    {
        $eventDrivenActionModel = new EventDrivenActionModel();
        $eventDrivenActionModel->load($actionId);

        if (! $eventDrivenActionModel->loaded()) {
            $this->addDebugLogMessage('Event driven action not found');

            return;
        }

        $eventDrivenActionModel->markAsCancelled();
    }

    private function getNotificationOptions(array $nodeSettings): array
    {
        $notificationOptions = $nodeSettings['options'] ?? [];

        if (empty($notificationOptions)) {
            $notificationOptions = [
                [
                    'name' => 'dismiss',
                    'label' => __('Dismiss', 'publishpress-future-pro'),
                    'hint' => __('Dismiss the notification', 'publishpress-future-pro'),
                ],
            ];
        }

        return $notificationOptions;
    }

    private function getNotificationType(array $nodeSettings): string
    {
        $notificationType = $nodeSettings['notificationType'] ?? NotificationModel::NOTIFICATION_TYPE_DEFAULT;

        if (! in_array($notificationType, NotificationModel::VALID_NOTIFICATION_TYPES)) {
            $notificationType = NotificationModel::NOTIFICATION_TYPE_DEFAULT;
        }

        return $notificationType;
    }
}
