<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Console;

use App\Shared\Infrastructure\Messenger\ClassNameHelper;

use function count;

use Doctrine\DBAL\Connection;

use function is_array;
use function is_numeric;
use function is_string;

use const JSON_PRETTY_PRINT;
use const JSON_UNESCAPED_UNICODE;

use Override;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * Reviews and inspects failed messages in the dead-letter queue.
 *
 * Provides list and show subcommands for failed messages
 * stored in the messenger_messages table (queue_name = 'failed').
 *
 * For retry/delete operations, use the built-in Symfony commands:
 *   - messenger:failed:retry
 *   - messenger:failed:remove
 */
#[AsCommand(
    name: 'app:messenger:review-failed',
    description: 'List or inspect failed messages in the dead-letter queue',
)]
final class ReviewFailedMessagesCommand extends Command
{
    public function __construct(
        private readonly Connection $connection,
    ) {
        parent::__construct();
    }

    #[Override]
    protected function configure(): void
    {
        $this
            ->addArgument(
                'action',
                InputArgument::OPTIONAL,
                'Action: list (default), show',
                'list',
            )
            ->addOption(
                'id',
                null,
                InputOption::VALUE_REQUIRED,
                'Message ID (required for show)',
            )
            ->addOption(
                'limit',
                'l',
                InputOption::VALUE_REQUIRED,
                'Maximum number of messages to display',
                '20',
            )
            ->setHelp(<<<'HELP'
                The <info>%command.name%</info> command lists and inspects failed messages.

                To retry or delete a failed message, use the built-in Symfony Messenger commands:
                  <info>php bin/console messenger:failed:retry {id}</info>
                  <info>php bin/console messenger:failed:remove {id}</info>
                HELP);
    }

    #[Override]
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        /** @var string $action */
        $action = $input->getArgument('action');

        return match ($action) {
            'list' => $this->listMessages($input, $io),
            'show' => $this->showMessage($input, $io),
            default => $this->invalidAction($action, $io),
        };
    }

    private function listMessages(InputInterface $input, SymfonyStyle $io): int
    {
        /** @var string $limitOption */
        $limitOption = $input->getOption('limit');
        if (!is_numeric($limitOption) || (int) $limitOption < 1) {
            $io->error('L\'option --limit doit être un entier positif.');

            return Command::FAILURE;
        }
        $limit = (int) $limitOption;

        $rows = $this->connection->fetchAllAssociative(
            'SELECT id, headers, created_at FROM messenger_messages WHERE queue_name = :queue ORDER BY created_at DESC LIMIT :limit',
            ['queue' => 'failed', 'limit' => $limit],
        );

        if ($rows === []) {
            $io->success('Aucun message en echec.');

            return Command::SUCCESS;
        }

        $tableRows = [];
        foreach ($rows as $row) {
            $headersRaw = is_string($row['headers']) ? $row['headers'] : '';
            $headers = $this->decodeHeaders($headersRaw);
            $type = $headers['type'] ?? 'inconnu';

            $tableRows[] = [
                $row['id'],
                ClassNameHelper::short($type),
                $row['created_at'],
            ];
        }

        $io->table(['ID', 'Type', 'Date'], $tableRows);
        $io->info(count($rows) . ' message(s) en echec.');
        $io->note('Pour rejouer ou supprimer : messenger:failed:retry {id} / messenger:failed:remove {id}');

        return Command::SUCCESS;
    }

    private function showMessage(InputInterface $input, SymfonyStyle $io): int
    {
        $id = $this->requireId($input, $io);
        if ($id === null) {
            return Command::FAILURE;
        }

        $row = $this->connection->fetchAssociative(
            'SELECT * FROM messenger_messages WHERE id = :id AND queue_name = :queue',
            ['id' => $id, 'queue' => 'failed'],
        );

        if ($row === false) {
            $io->error("Message #{$id} introuvable dans la dead-letter queue.");

            return Command::FAILURE;
        }

        $headersRaw = is_string($row['headers']) ? $row['headers'] : '';
        $headers = $this->decodeHeaders($headersRaw);
        $type = $headers['type'] ?? 'inconnu';

        $io->title("Message #{$id}");
        $io->definitionList(
            ['Type' => ClassNameHelper::short($type)],
            ['Date de creation' => $row['created_at']],
            ['Date de livraison' => $row['delivered_at'] ?? 'N/A'],
        );

        $io->section('Headers');
        $io->text(json_encode($headers, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) ?: '{}');

        $io->note('Pour rejouer : messenger:failed:retry ' . $id);
        $io->note('Pour supprimer : messenger:failed:remove ' . $id);

        return Command::SUCCESS;
    }

    private function invalidAction(string $action, SymfonyStyle $io): int
    {
        $io->error("Action inconnue : '{$action}'. Actions valides : list, show");

        return Command::FAILURE;
    }

    private function requireId(InputInterface $input, SymfonyStyle $io): ?int
    {
        /** @var string|null $idOption */
        $idOption = $input->getOption('id');
        if ($idOption === null) {
            $io->error("L'option --id est requise pour cette action.");

            return null;
        }

        if (!is_numeric($idOption) || (int) $idOption < 1) {
            $io->error("L'option --id doit être un entier positif.");

            return null;
        }

        return (int) $idOption;
    }

    /**
     * @return array<string, string>
     */
    private function decodeHeaders(string $json): array
    {
        $decoded = json_decode($json, true);

        if (!is_array($decoded)) {
            return [];
        }

        $result = [];
        foreach ($decoded as $key => $value) {
            $result[(string) $key] = is_string($value) ? $value : '';
        }

        return $result;
    }
}
