<?php

namespace idoit\Module\JDisc\Model;

use idoit\Model\Dao\Base;

/**
 * Class CommandQueue
 *
 * Manages the command queue.
 *
 * @copyright synetics GmbH
 * @license   http://www.i-doit.com/license
 * @author    Paul Kolbovich <pkolbovich@i-doit.org>
 */
class CommandQueue extends Base
{
    public const STATUS_PENDING = 0;
    public const STATUS_PROCESSING = 1;
    public const STATUS_COMPLETED = 2;
    public const STATUS_FAILED = 3;

    protected float $minCheckInterval = 1 / 3;
    protected float $lastWriteTime = 0;
    protected string $queue = 'default';

    /**
     * @param float $minCheckInterval
     * @return void
     */
    public function setMinCheckInterval(float $minCheckInterval = 0): void
    {
        $this->minCheckInterval = $minCheckInterval;
    }

    /**
     * @param string $queue
     * @return void
     */
    public function setQueue(string $queue): void
    {
        $this->queue = $this->m_db->escape_string($queue);
    }

    /**
     * @param string $command
     * @param mixed $payload
     * @return void
     */
    public function enqueue(string $command, $payload = null): void
    {
        if ($payload !== null) {
            $payload = serialize($payload);
        }

        $status = self::STATUS_PENDING;
        $command = $this->m_db->escape_string($command);

        $sql = "INSERT INTO `isys_command_queue` (`isys_command_queue__command`, `isys_command_queue__queue`, `isys_command_queue__payload`, `isys_command_queue__status`)
                VALUES ('{$command}', '{$this->queue}', '{$payload}', '{$status}')";

        $this->update($sql);
        $this->apply_update();
    }

    /**
     * @return array|null
     */
    public function dequeue(): ?array
    {
        $timeInterval = microtime(true) - $this->lastWriteTime;

        if ($timeInterval < $this->minCheckInterval) {
            return null;
        }
        $this->lastWriteTime = microtime(true);

        $status = self::STATUS_PENDING;

        $sql = "SELECT * FROM `isys_command_queue` WHERE `isys_command_queue__status` = '{$status}' AND `isys_command_queue__queue` = '{$this->queue}' LIMIT 1 FOR UPDATE";
        $data = $this->retrieve($sql)->get_row();
        if ($data) {
            // Update status to processing to avoid duplicates
            $this->updateStatus($data['isys_command_queue__id'], self::STATUS_PROCESSING);
        }
        return $data;
    }

    /**
     * @param int $id
     * @param int $status
     * @return void
     * @throws \InvalidArgumentException
     */
    public function updateStatus(int $id, int $status): void
    {
        if (!in_array($status, [self::STATUS_PENDING, self::STATUS_PROCESSING, self::STATUS_COMPLETED, self::STATUS_FAILED])) {
            throw new \InvalidArgumentException('Invalid status provided.');
        }

        $sql = "UPDATE `isys_command_queue` SET `isys_command_queue__status` = '{$status}', `isys_command_queue__updated_at` = CURRENT_TIMESTAMP WHERE `isys_command_queue__id` = '{$id}'";
        $this->update($sql);
        $this->apply_update();
    }

    /**
     * @param int $id
     * @return void
     */
    public function delete(int $id): void
    {
        $sql = "DELETE FROM `isys_command_queue` WHERE `isys_command_queue__id` = '{$id}'";
        $this->update($sql);
        $this->apply_update();
    }
}
