<?php

declare(strict_types=1);

namespace NoahVet\Reef\Command\RabbitMQ;

use NoahVet\Reef\RabbitMQ\Notification\Event\ReefNotificationEvent;
use NoahVet\Reef\RabbitMQ\Notification\ReefNotification;
use NoahVet\Reef\RabbitMQ\Notification\ReefNotificationInterface;
use NoahVet\Reef\RabbitMQ\Task\ConsoleCommandTask;
use NoahVet\Reef\RabbitMQ\Task\Executor\ConsoleCommandTaskExecutorInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

#[AsCommand(name: 'reef:rabbitmq:handle-message')]
class RabbitMQHandleMessageCommand extends Command
{
    protected const DENORMALIZABLE = [
        ConsoleCommandTask::class,
        ReefNotification::class,
    ];

    public function __construct(
        private readonly EventDispatcherInterface $eventDispatcher,
        private readonly LoggerInterface $logger,
        private readonly SerializerInterface&DenormalizerInterface $serializer,
        private readonly ConsoleCommandTaskExecutorInterface $taskExecutor,
    ) {
        parent::__construct('reef:rabbitmq:handle-message');
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $styledOutput = new SymfonyStyle($input, $output);

        $rawMessage = \stream_get_contents(\STDIN);

        if (false === $rawMessage) {
            $styledOutput->error('Error reading message payload from php://input');

            return Command::FAILURE;
        }

        $parsed = \json_decode($rawMessage, true);

        if (
            !\is_array($parsed)
            || !\in_array($parsed['type'] ?? null, self::DENORMALIZABLE)
        ) {
            $this->logger->debug('AMQP Message cannot be processed', [
                'message' => $rawMessage,
            ]);

            return Command::FAILURE;
        }

        try {
            /* @var ConsoleCommandTask|ReefNotification $object */
            $object = $this->serializer->denormalize(
                $parsed,
                $parsed['type'],
                'json',
            );
        } catch (\Exception|ExceptionInterface $e) {
            $this->logger->error('Failed to denormalize AMQP Message', [
                'exception' => $e,
                'message' => $rawMessage,
            ]);

            return Command::FAILURE;
        }
        if ($object instanceof ConsoleCommandTask) {
            $this->logger->debug(
                'Found internal task',
                [
                    'class' => $object::class,
                ],
            );

            return $this->taskExecutor->execute($object, $output);
        } elseif ($object instanceof ReefNotificationInterface) {
            $this->logger->debug(
                'Found notification',
                [
                    'class' => $object::class,
                    'resource' => (string) $object->getResource(),
                    'action' => $object->getAction(),
                    'rawData' => $object->getRawData(),
                ],
            );

            $event = new ReefNotificationEvent($object);
        } else {
            $this->logger->warning(
                'Unable to deserialize message body',
                [
                    'body' => $rawMessage,
                ],
            );

            return Command::FAILURE;
        }

        try {
            $this->eventDispatcher->dispatch($event);
        } catch (\Exception $e) {
            $this->logger->error($e->getMessage(), [
                'message' => $rawMessage,
            ]);
        }

        return Command::SUCCESS;
    }

    protected function configure(): void
    {
        $this->setDescription('Internal process handling RabbitMQ\'s messages');
    }
}
