<?php

/**
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; under version 2
 * of the License (non-upgradable).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Copyright (c) 2020 (original work) Open Assessment Technologies SA;
 */

declare(strict_types=1);

namespace oat\taoTaskQueue\model\Service;

use common_exception_Error;
use InvalidArgumentException;
use oat\oatbox\action\Action;
use oat\oatbox\service\ConfigurableService;
use oat\tao\model\taskQueue\Queue;
use oat\tao\model\taskQueue\QueueDispatcher;
use oat\tao\model\taskQueue\QueueDispatcherInterface;
use oat\taoTaskQueue\model\QueueBroker\RdsQueueBroker;
use oat\taoTaskQueue\scripts\tools\BrokerFactory;
use oat\taoTaskQueue\scripts\tools\InitializeQueue;

class QueueAssociationService extends ConfigurableService
{
    public function associate(string $taskClass, string $queue): InitializeQueue
    {
        $targetClass = $this->getTargetClass($taskClass);

        $existingQueues = $this->getQueueDispatcher()->getOption(QueueDispatcherInterface::OPTION_QUEUES);
        $newQueue = new Queue($queue, new RdsQueueBroker('default', 1), 30);
        $existingOptions = $this->getQueueDispatcher()->getOptions();
        $existingOptions[QueueDispatcherInterface::OPTION_QUEUES] = array_unique(
            array_merge($existingQueues, [$newQueue])
        );
        $existingAssociations = $this->getQueueDispatcher()->getOption(QueueDispatcherInterface::OPTION_TASK_TO_QUEUE_ASSOCIATIONS);
        $existingOptions[QueueDispatcherInterface::OPTION_TASK_TO_QUEUE_ASSOCIATIONS] = array_merge(
            $existingAssociations,
            [$targetClass => $queue]
        );
        $this->getQueueDispatcher()->setOptions($existingOptions);

        $this->getServiceManager()->register(QueueDispatcherInterface::SERVICE_ID, $this->getQueueDispatcher());

        $initializer = new InitializeQueue();
        $this->propagate($initializer);
        return $initializer;
    }

    private function getTargetClass(string $taskClass): string
    {
        if (class_exists($taskClass) && is_a($taskClass, Action::class, true)) {
            return $taskClass;
        }
        throw new InvalidArgumentException(
            sprintf('%s - Task must extend %s', $taskClass, Action::class)
        );
    }

    private function getQueueDispatcher(): QueueDispatcher
    {
        return $this->getServiceLocator()->get(QueueDispatcher::SERVICE_ID);
    }

    /**
     * @throws common_exception_Error
     */
    public function associateBulk(
        string $newQueueName,
        array $newAssociations
    ): ?Queue {
        $factory = $this->getBrokerFactory();
        $queueService = $this->getQueueDispatcher();
        $existingOptions = $queueService->getOptions();
        $existingQueues = $queueService->getOption(QueueDispatcherInterface::OPTION_QUEUES);
        $newQueue = null;

        if (!in_array($newQueueName, $queueService->getQueueNames())){
            $broker = $factory->create($this->guessDefaultBrokerType(), 'default', 2);
            $newQueue = new Queue($newQueueName, $broker, 30);
            $this->propagate($broker);

            $existingOptions[QueueDispatcherInterface::OPTION_QUEUES] = array_merge($existingQueues, [$newQueue]);
        }

        $existingAssociations = $existingOptions[QueueDispatcherInterface::OPTION_TASK_TO_QUEUE_ASSOCIATIONS];

        $existingOptions[QueueDispatcherInterface::OPTION_TASK_TO_QUEUE_ASSOCIATIONS] = array_merge(
            $existingAssociations,
            $newAssociations
        );

        $queueService->setOptions($existingOptions);
        $this->getServiceManager()->register(QueueDispatcherInterface::SERVICE_ID, $queueService);

        return $newQueue;
    }

    public function guessDefaultBrokerType(): string
    {
        $queueService = $this->getQueueDispatcher();

        $existingQueues = $queueService->getOption(QueueDispatcherInterface::OPTION_QUEUES);
        /** @var Queue $queue */
        $queue = $existingQueues[0];

        $this->propagate($queue);

        return $queue->getBroker()->getBrokerId();
    }

    public function deleteAndRemoveAssociations(string $queueNameForRemoval): void
    {
        /** @var QueueDispatcher $queueService */
        $queueService = $this->getServiceManager()->get(QueueDispatcher::SERVICE_ID);
        $existingQueues = $queueService->getOption(QueueDispatcherInterface::OPTION_QUEUES);

        $newQueue = [];
        /** @var Queue $queue */
        foreach ($existingQueues as $queue) {
            if ($queue->getName() !== $queueNameForRemoval) {
                $newQueue[] = $queue;
            }
        }

        $existingOptions = $queueService->getOptions();
        $existingOptions[QueueDispatcherInterface::OPTION_QUEUES] = $newQueue;

        $existingAssociations = $existingOptions[QueueDispatcherInterface::OPTION_TASK_TO_QUEUE_ASSOCIATIONS];
        $newAssociations = array_filter(
            $existingAssociations,
            function ($queueName) use ($queueNameForRemoval) {
                return $queueNameForRemoval !== $queueName;
            }
        );
        $existingOptions[QueueDispatcherInterface::OPTION_TASK_TO_QUEUE_ASSOCIATIONS] = $newAssociations;

        $queueService->setOptions($existingOptions);
        $this->getServiceManager()->register(QueueDispatcherInterface::SERVICE_ID, $queueService);
    }

    private function getBrokerFactory(): BrokerFactory
    {
        return $this->getServiceManager()->get(BrokerFactory::class);
    }
}