<?php

namespace FSPoster\App\Pages\Settings\Workflow\Services;

use FSPoster\App\Pages\Settings\Workflow\DTOs\Response\WorkflowActionResponse;
use FSPoster\App\Pages\Settings\Workflow\Repositories\WorkflowQueueRepository;
use FSPoster\App\Providers\Core\BackgroundProcess;
use FSPoster\App\Providers\DB\Collection;
use FSPoster\App\Providers\Helpers\Date;
use FSPoster\App\Providers\Helpers\Helper;

class WorkflowQueueService
{
    private WorkflowQueueRepository $repository;
    private WorkflowScheduleTimeResolver $timeResolver;
    private WorkflowEventMetadataRegistry $metadataRegistry;
    private ?array $executionContext = null;

    public function __construct(
        WorkflowQueueRepository $repository,
        WorkflowScheduleTimeResolver $timeResolver,
        WorkflowEventMetadataRegistry $metadataRegistry
    ) {
        $this->repository = $repository;
        $this->timeResolver = $timeResolver;
        $this->metadataRegistry = $metadataRegistry;
    }

    public function enqueueWorkflowAction(
        Collection $workflow,
        WorkflowActionResponse $action,
        string $eventKey,
        array $eventData,
        ?int $plannedAt = null,
        ?array $overrideActionData = null
    ): void {
        $eventData = $this->normalizeEventData($eventData);
        $actionData = $overrideActionData ?? $action->getData();

        $plannedEpoch = $plannedAt ?? $this->timeResolver->resolve($workflow, $eventKey, $eventData);

        if ($plannedEpoch <= Date::epoch()) {
            static $dispatched = false;
            if (!$dispatched) {
                $dispatched = true;
                add_action('shutdown', static function () {
                    (new BackgroundProcess())->dispatch();
                });
            }
        }

        $requestData = wp_json_encode([
            'eventData' => $eventData,
            'actionSettingsData' => $actionData,
        ]);

        $originScheduleId = $this->executionContext['origin_schedule_id'] ?? null;
        $chainDepth = $this->executionContext['chain_depth'] ?? 0;

        $payload = [
            'workflow_id' => (int)$workflow['id'],
            'workflow_action_id' => $action->getId(),
            'when' => $eventKey,
            'driver' => $action->getDriver(),
            'request_data' => $requestData,
            'response_data' => null,
            'data' => wp_json_encode($actionData),
            'event_data' => wp_json_encode($eventData),
            'planned_at' => Date::dateTimeSQL($plannedEpoch),
            'executed_at' => null,
            'status' => 'pending',
            'error_msg' => null,
            'attempt_count' => 0,
            'dedupe_key' => $this->buildDedupeKey($workflow, $action, $eventKey, $plannedEpoch, $eventData, $actionData),
            'lock_token' => null,
            'locked_at' => null,
            'schedule_id' => isset($eventData['schedule_id']) ? (int)$eventData['schedule_id'] : null,
            'user_id' => isset($eventData['user_id']) ? (int)$eventData['user_id'] : null,
            'origin_schedule_id' => !empty($originScheduleId) ? (int)$originScheduleId : null,
            'chain_depth' => max(0, (int)$chainDepth),
            'blog_id' => !empty($workflow['blog_id']) ? (int)$workflow['blog_id'] : Helper::getBlogId(),
        ];

        $this->repository->insertOrIgnore($payload);
    }

    public function enqueueRetry(array $payload): void
    {
        $plannedAt = Date::epoch();
        $eventData = json_decode((string)($payload['event_data'] ?? '[]'), true);
        $eventData = is_array($eventData) ? $eventData : [];
        $eventData = $this->normalizeEventData($eventData, [
            'queue_retry_count' => ((int)($payload['attempt_count'] ?? 0)) + 1,
            'queue_retry_reason' => (string)($payload['error_msg'] ?? ''),
        ]);

        $data = [
            'workflow_id' => (int)$payload['workflow_id'],
            'workflow_action_id' => !empty($payload['workflow_action_id']) ? (int)$payload['workflow_action_id'] : null,
            'when' => (string)$payload['when'],
            'driver' => (string)$payload['driver'],
            'request_data' => wp_json_encode([
                'eventData' => $eventData,
                'actionSettingsData' => json_decode((string)($payload['data'] ?? '[]'), true) ?: [],
            ]),
            'response_data' => null,
            'data' => (string)($payload['data'] ?? ''),
            'event_data' => wp_json_encode($eventData),
            'planned_at' => Date::dateTimeSQL($plannedAt),
            'executed_at' => null,
            'status' => 'pending',
            'error_msg' => null,
            'attempt_count' => 0,
            'dedupe_key' => 'retry|' . md5(wp_json_encode($payload) . '|' . microtime(true)),
            'lock_token' => null,
            'locked_at' => null,
            'schedule_id' => !empty($payload['schedule_id']) ? (int)$payload['schedule_id'] : null,
            'user_id' => !empty($payload['user_id']) ? (int)$payload['user_id'] : null,
            'origin_schedule_id' => !empty($payload['origin_schedule_id']) ? (int)$payload['origin_schedule_id'] : null,
            'chain_depth' => isset($payload['chain_depth']) ? max(0, (int)$payload['chain_depth']) : 0,
            'blog_id' => !empty($payload['blog_id']) ? (int)$payload['blog_id'] : Helper::getBlogId(),
        ];

        $this->repository->insertOrIgnore($data);
    }

    public function cancelPendingByWorkflowId(int $workflowId): void
    {
        $this->repository->cancelPendingByWorkflowId($workflowId);
    }

    public function cancelPendingByWorkflowActionId(int $workflowActionId): void
    {
        $this->repository->cancelPendingByWorkflowActionId($workflowActionId);
    }

    public function beginExecutionContext(int $currentScheduleId, ?int $originScheduleId, int $currentChainDepth): void
    {
        $rootOriginScheduleId = $originScheduleId ?? $currentScheduleId;

        $this->executionContext = [
            'origin_schedule_id' => $rootOriginScheduleId,
            'chain_depth' => max(0, $currentChainDepth + 1),
        ];
    }

    public function clearExecutionContext(): void
    {
        $this->executionContext = null;
    }

    private function buildDedupeKey(
        Collection $workflow,
        WorkflowActionResponse $action,
        string $eventKey,
        int $plannedEpoch,
        array $eventData,
        array $actionData = []
    ): string {
        $parts = [
            'workflow:' . (int)$workflow['id'],
            'action:' . $action->getId(),
            'when:' . $eventKey,
            'planned:' . $plannedEpoch,
        ];

        foreach (['schedule_id', 'user_id'] as $key) {
            if (isset($eventData[$key]) && $eventData[$key] !== '') {
                $parts[] = $key . ':' . $eventData[$key];
            }
        }

        if (count($parts) === 4) {
            $parts[] = 'payload:' . md5(wp_json_encode($eventData));
        }

        $parts[] = 'data:' . md5(wp_json_encode($actionData));

        return implode('|', $parts);
    }

    private function normalizeEventData(array $eventData, array $extra = []): array
    {
        if (empty($eventData['queue_execution_key'])) {
            $eventData['queue_execution_key'] = wp_generate_uuid4();
        }

        if (empty($eventData['queue_enqueued_at'])) {
            $eventData['queue_enqueued_at'] = Date::dateTimeSQL();
        }

        return array_merge($eventData, $extra);
    }
}
