<?php
declare(strict_types=1);

/**
 * BEdita, API-first content management framework
 * Copyright 2022 ChannelWeb Srl, Chialab Srl
 *
 * This file is part of BEdita: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
 */

namespace BEdita\Core\Job;

use Cake\Datasource\Exception\RecordNotFoundException;
use Cake\Log\LogTrait;
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\Queue\Job\JobInterface;
use Cake\Queue\Job\Message;
use Cake\Utility\Hash;
use Interop\Queue\Processor;

/**
 * Default Queue Job class consuming AsyncJobs
 *
 * @property \BEdita\Core\Model\Table\AsyncJobsTable $AsyncJobs
 */
#[\AllowDynamicProperties]
class QueueJob implements JobInterface
{
    use LocatorAwareTrait;
    use LogTrait;

    /**
     * @inheritDoc
     */
    public function execute(Message $message): ?string
    {
        $this->AsyncJobs = $this->fetchTable('AsyncJobs');
        $uuid = $message->getArgument('uuid');
        $this->log(sprintf('Processing job "%s"', $uuid), 'debug');
        $success = false;
        try {
            $success = $this->run($uuid);
        } catch (RecordNotFoundException $e) {
            $this->log(sprintf('Could not obtain lock on job "%s"', $uuid), 'warning');

            return Processor::REJECT;
        }

        if ($success) {
            return Processor::ACK;
        }

        try {
            $asyncJob = $this->AsyncJobs->get($uuid);
        } catch (RecordNotFoundException $e) {
            $this->log(sprintf('Job "%s" not found', $uuid), 'warning');

            return Processor::REJECT;
        }

        return $asyncJob->max_attempts > 0 ? Processor::REQUEUE : Processor::REJECT;
    }

    /**
     * Process a single pending job.
     *
     * @param string $uuid Job UUID.
     * @return bool
     */
    protected function run($uuid): bool
    {
        $asyncJob = $this->AsyncJobs->lock($uuid);
        $success = false;
        $messages = [];
        try {
            $result = $asyncJob->run();
            $success = is_bool($result) ? $result : (bool)Hash::get((array)$result, 'success');
            $messages = is_array($result) ? (array)Hash::get($result, 'messages') : [];
        } catch (\Exception $e) {
            $messages[] = $e->getMessage();
            $this->log(sprintf('Error running job "%s" - %s', $uuid, $e->getMessage()), 'error');
        } finally {
            $result = $success ? 'completed successfully' : 'failed';
            $message = sprintf('Job "%s" [%s] %s', $asyncJob->uuid, $asyncJob->service, $result);
            $messages[] = $message;
            $this->AsyncJobs->updateResults($asyncJob, $success, $messages);
            $this->log($message, 'debug');
            $this->AsyncJobs->unlock($asyncJob->uuid, $success);
        }

        return $success;
    }
}
