<?php
namespace DevOwl\RealPhysicalMedia\rest;

use DevOwl\RealPhysicalMedia\base\UtilsProvider;
use DevOwl\RealPhysicalMedia\configuration\Options;
use DevOwl\RealPhysicalMedia\listener\Lockfile;
use DevOwl\RealPhysicalMedia\queue\Queue as QueueQueue;
use DevOwl\RealPhysicalMedia\rest\Service as RestService;
use DevOwl\RealPhysicalMedia\Util;
use DevOwl\RealPhysicalMedia\view\AdminBar;
use MatthiasWeb\RealMediaLibrary\rest\Service;
use MatthiasWeb\Utils\Service as UtilsService;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

// @codeCoverageIgnoreStart
defined('ABSPATH') or die('No script kiddies please!'); // Avoid direct file request
// @codeCoverageIgnoreEnd

/**
 * Main service.
 */
class Queue {
    use UtilsProvider;

    /**
     * C'tor.
     */
    private function __construct() {
        // Silence is golden.
    }

    /**
     * Register endpoints.
     */
    public function rest_api_init() {
        $namespace = UtilsService::getNamespace($this);

        register_rest_route($namespace, '/queue', [
            'methods' => 'GET',
            'callback' => [$this, 'routeQueue'],
            'permission_callback' => [$this, 'permission_callback'],
        ]);

        register_rest_route($namespace, '/queue', [
            'methods' => 'POST',
            'callback' => [$this, 'routeQueuePost'],
            'permission_callback' => [$this, 'permission_callback'],
        ]);

        register_rest_route($namespace, '/queue/automatic', [
            'methods' => 'POST',
            'callback' => [$this, 'routeQueueAutomatic'],
            'permission_callback' => [$this, 'permission_callback_manage_options'],
            'args' => [
                'state' => [
                    'type' => 'boolean',
                ],
            ],
        ]);

        register_rest_route($namespace, '/queue/automatic-hint/dismiss', [
            'methods' => 'DELETE',
            'callback' => [$this, 'routeQueueAutomaticHintDismiss'],
            'permission_callback' => [$this, 'permission_callback_manage_options'],
        ]);

        register_rest_route($namespace, '/queue/notice/dismiss', [
            'methods' => 'DELETE',
            'callback' => [$this, 'routeQueueNoticeDismiss'],
            'permission_callback' => [$this, 'permission_callback_manage_options'],
        ]);

        register_rest_route($namespace, '/queue/item/(?P<id>\d+)', [
            'methods' => 'POST',
            'callback' => [$this, 'routeQueueItemAdd'],
            'permission_callback' => [$this, 'permission_callback'],
        ]);

        register_rest_route($namespace, '/queue/item/potential', [
            'methods' => 'POST',
            'callback' => [$this, 'routeQueueItemAddPotential'],
            'permission_callback' => [$this, 'permission_callback_manage_options'],
        ]);

        register_rest_route($namespace, '/queue/cron/(?P<token>[A-za-z0-9]+)', [
            'methods' => 'GET',
            'callback' => [$this, 'routeQueueCron'],
            'permission_callback' => '__return_true',
        ]);

        register_rest_route($namespace, '/lockfiles/reflect', [
            'methods' => 'POST',
            'callback' => [$this, 'routeLockfilesReflect'],
            'permission_callback' => [$this, 'permission_callback_manage_options'],
        ]);

        register_rest_route($namespace, '/lockfiles', [
            'methods' => 'DELETE',
            'callback' => [$this, 'routeLockfilesDelete'],
            'permission_callback' => [$this, 'permission_callback_manage_options'],
        ]);
    }

    /**
     * Check if user is allowed to call this service requests.
     */
    public function permission_callback() {
        $permit = Service::permit();
        return $permit === null ? true : $permit;
    }

    /**
     * Check if user is allowed to call this service requests with `manage_options` cap.
     */
    public function permission_callback_manage_options() {
        $permit = Service::permit();
        return $permit === null ? true : $permit;
    }

    /**
     * See API docs.
     *
     * @param WP_REST_Request $request
     * @api {get} /real-physical-media/v1/queue Get queue information
     * @apiHeader {string} X-WP-Nonce
     * @apiParam {int} [skip=0] Offset
     * @apiParam {int} [top=5] Top, maximum 100
     * @apiName GetQueue
     * @apiGroup Queue
     * @apiVersion 1.0.0
     * @apiPermission upload_files
     */
    public function routeQueue($request) {
        $queue = QueueQueue::getInstance();
        return new WP_REST_Response($queue->prepareResponse($request->get_param('skip'), $request->get_param('top')));
    }

    /**
     * See API docs.
     *
     * @param WP_REST_Request $request
     * @api {post} /real-physical-media/v1/queue Process the queue and return back updating rows
     * @apiHeader {string} X-WP-Nonce
     * @apiParam {int} [skip=0] Offset
     * @apiParam {int} [top=5] Top, maximum 100
     * @apiParam {string} [skip] The skip mechanism: 'retry' or 'skip'
     * @apiName PostQueue
     * @apiGroup Queue
     * @apiVersion 1.0.0
     * @apiPermission upload_files
     */
    public function routeQueuePost($request) {
        $do = $this->doQueue($request);
        if (is_wp_error($do)) {
            return $do;
        }
        return new WP_REST_Response($do);
    }

    /**
     * See API docs.
     *
     * @param WP_REST_Request $request
     * @api {get} /real-physical-media/v1/queue/cron/:token Process the queue for cron URL
     * @apiHeader {string} X-WP-Nonce
     * @apiParam {string} token The unique token for the given blog
     * @apiParam {string} [skip=0] Offset
     * @apiParam {int} [top=5] Top, maximum 100
     * @apiParam {string} [skip] The skip mechanism: 'retry' or 'skip'
     * @apiName CronQueue
     * @apiGroup Queue
     * @apiVersion 1.0.0
     */
    public function routeQueueCron($request) {
        if (Options::getInstance()->getCronJobToken() !== $request->get_param('token')) {
            return new WP_Error(
                'rpm_cron_queue_process',
                __(
                    'You are not allowed to do this. You can find your cronjob service URLs in the media settings.',
                    RPM_TD
                ),
                ['status' => 403]
            );
        }

        $do = $this->doQueue($request);
        if (is_wp_error($do)) {
            return $do;
        }
        return new WP_REST_Response($do);
    }

    /**
     * See API docs.
     *
     * @param WP_REST_Request $request
     * @api {post} /real-physical-media/v1/queue/item/:id Add an item to the queue
     * @apiHeader {string} X-WP-Nonce
     * @apiParam {int} id The attachment id
     * @apiName AddItem
     * @apiGroup Queue
     * @apiVersion 1.0.0
     * @apiPermission upload_files
     */
    public function routeQueueItemAdd($request) {
        if (($permit = Service::permit()) !== null) {
            return $permit;
        }

        $queue = QueueQueue::getInstance();

        // Add item to queue
        $result = $queue->add($request->get_param('id'));
        if (is_wp_error($result)) {
            return $result;
        }

        return new WP_REST_Response(true);
    }

    /**
     * See API docs.
     *
     * @param WP_REST_Request $request
     * @api {post} /real-physical-media/v1/queue/item/potential Reinitialize and add all potential files from this blog to the queue
     * @apiHeader {string} X-WP-Nonce
     * @apiName AddPotentialItems
     * @apiGroup Queue
     * @apiVersion 1.0.0
     * @apiPermission manage_options
     */
    public function routeQueueItemAddPotential($request) {
        if (($permit = Service::permit('manage_options')) !== null) {
            return $permit;
        }
        if (($demo = RestService::demoNotAllowed()) !== null) {
            return $demo;
        }

        QueueQueue::getInstance()->addPotentialFiles();
        return new WP_REST_Response(true);
    }

    /**
     * See API docs.
     *
     * @param WP_REST_Request $request
     * @api {post} /real-physical-media/v1/queue/automatic Activate or deactivate the automatic queueing
     * @apiHeader {string} X-WP-Nonce
     * @apiParam {boolean} state The new state
     * @apiName SetAutomaticQueueing
     * @apiGroup Queue
     * @apiVersion 1.0.0
     * @apiPermission manage_options
     */
    public function routeQueueAutomatic($request) {
        if (($permit = Service::permit('manage_options')) !== null) {
            return $permit;
        }

        $queue = QueueQueue::getInstance();

        // Add item to queue
        $result = $queue->isAutomaticQueueing($request->get_param('state'));
        if (is_wp_error($result)) {
            return $result;
        }

        return new WP_REST_Response($result);
    }

    /**
     * See API docs.
     *
     * @param WP_REST_Request $request
     * @api {delete} /real-physical-media/v1/queue/automatic-hint/dismiss Dismiss the hint to automatic queueing
     * @apiName DismissAutomaticHint
     * @apiGroup Queue
     * @apiVersion 1.1.0
     * @apiPermission manage_options
     */
    public function routeQueueAutomaticHintDismiss() {
        if (($permit = Service::permit('manage_options')) !== null) {
            return $permit;
        }
        AdminBar::getInstance()->isFirstTimeMoveHintDismissed(true);
        return new WP_REST_Response(true);
    }

    /**
     * See API docs.
     *
     * @param WP_REST_Request $request
     * @api {delete} /real-physical-media/v1/queue/notice/dismiss Dismiss the notice to queue admin bar
     * @apiName DismissQueueNotice
     * @apiGroup Queue
     * @apiVersion 1.1.0
     * @apiPermission manage_options
     */
    public function routeQueueNoticeDismiss() {
        if (($permit = Service::permit('manage_options')) !== null) {
            return $permit;
        }
        AdminBar::getInstance()->isFirstTimeQueueNoticeDismissed(true);
        return new WP_REST_Response(true);
    }

    /**
     * See API docs.
     *
     * @param WP_REST_Request $request
     * @api {post} /real-physical-media/v1/lockfiles/reflect Reflect the complete virtual structure to physical folders
     * @apiHeader {string} X-WP-Nonce
     * @apiParam {boolean} withlockfile Create lockfiles if true
     * @apiName ReflectLockfiles
     * @apiGroup Lockfiles
     * @apiVersion 1.0.0
     * @apiPermission manage_options
     */
    public function routeLockfilesReflect($request) {
        if (($permit = Service::permit('manage_options')) !== null) {
            return $permit;
        }
        Lockfile::getInstance()->createAll($request->get_param('withlockfile') === 'true');
        return new WP_REST_Response(true);
    }

    /**
     * See API docs.
     *
     * @param WP_REST_Request $request
     * @api {delete} /real-physical-media/v1/lockfiles Remove all lockfiles
     * @apiHeader {string} X-WP-Nonce
     * @apiName DeleteLockfiles
     * @apiGroup Lockfiles
     * @apiVersion 1.0.0
     * @apiPermission manage_options
     */
    public function routeLockfilesDelete($request) {
        if (($permit = Service::permit('manage_options')) !== null) {
            return $permit;
        }
        Lockfile::getInstance()->removeAll();
        return new WP_REST_Response(true);
    }

    /**
     * Process the queue.
     *
     * @param WP_REST_Request $request
     */
    private function doQueue($request) {
        $queue = QueueQueue::getInstance();

        // Delete error
        $skipError = $request->get_param('skip');
        if ($skipError === 'retry') {
            $queue->clearException();
        } elseif ($skipError === 'skip') {
            $queue->clearException(true);
        }

        // Get first entry and process is available
        $result = $queue->process(Options::getInstance()->getMaxExecTime());
        if (is_wp_error($result)) {
            return $result;
        }

        $response = $queue->prepareResponse($request->get_param('skip'), $request->get_param('top'));

        // Find common URL prefix to reduce payload
        $allDoneUrls = [];
        foreach ($result as $value) {
            $allDoneUrls = array_merge($allDoneUrls, array_values($value['u']));
        }
        $prefix = Util::commonPrefix($allDoneUrls);
        $prefln = strlen($prefix);
        foreach ($result as &$value) {
            foreach ($value['u'] as $size => $url) {
                $value['u'][$size] = substr($url, $prefln);
            }
        }

        $response['done'] = $result;
        $response['doneUrlPrefix'] = $prefix;
        return $response;
    }

    /**
     * New instance.
     */
    public static function instance() {
        return new Queue();
    }
}
