<?php

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Command;

use CpCreation\VitiCore\Seed\Model\SeedRun;
use CpCreation\VitiCore\Seed\Repository\SeedRunRepository;
use CpCreation\VitiCore\Seed\SeederInterface;
use CpCreation\VitiCore\Seed\SeederMetadata;
use CpCreation\VitiCore\Seed\SeedPayload;
use CpCreation\VitiCore\Seed\SeedRunner;
use Ramsey\Uuid\Uuid;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;

#[AsCommand(
    name: 'seed',
    description: 'Run Viticore seeders'
)]
final class SeedCommand extends Command
{
    /** @var array<string,bool>|null */
    private ?array $seederIndexMap = null;

    public function __construct(
        private readonly SeedRunner $seedRunner,
        private readonly SeedRunRepository $seedRunRepository,
        /** @var iterable<SeederInterface> */
        #[AutowireIterator(SeederInterface::class)]
        private readonly iterable $seeders,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('full', null, InputOption::VALUE_NONE, 'Run full seed for all projects (default: only active projects)')
            ->addOption('tag', null, InputOption::VALUE_OPTIONAL, 'Filter projects by tag')

            // Utility
            ->addOption('list', null, InputOption::VALUE_NONE, 'List all available seeders')
            ->addOption('silent', null, InputOption::VALUE_NONE, 'Do not display validation/help messages')

            // Seeder filter
            ->addOption('seeder', null, InputOption::VALUE_OPTIONAL, 'Run only one seeder');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // --silent behaves like -q for this command
        if ((bool) $input->getOption('silent')) {
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
        }

        // --list : print and exit
        if ((bool) $input->getOption('list')) {
            if (!$output->isQuiet()) {
                $output->writeln('<info>Available seeders:</info>');
                $output->writeln('');

                foreach ($this->seeders as $seeder) {
                    $metadata = SeederMetadata::fromSeeder($seeder);

                    $output->writeln(\sprintf(
                        ' <comment>%-20s</comment> v%-3d %s',
                        $metadata->index,
                        $seeder->version(),
                        $seeder->isEnabled() ? '<fg=green>enabled</>' : '<fg=yellow>disabled</>'
                    ));
                }
            }

            return Command::SUCCESS;
        }

        // Validation / show usage if needed
        $validation = $this->validateOptions($input, $output);
        if (null !== $validation) {
            return $validation; // SUCCESS or INVALID
        }

        $payload = new SeedPayload(
            runId: Uuid::uuid7()->toString(),
            tag: $this->stringOrNull($input->getOption('tag')),
            seeder: $this->stringOrNull($input->getOption('seeder')),
        );

        if (!$this->seedRunner->needsRun($payload)) {
            if (!$output->isQuiet()) {
                $output->writeln('<comment>Seed already up to date. Nothing to run.</comment>');
            }

            return Command::SUCCESS;
        }

        $run = SeedRun::pending($payload);
        $this->seedRunRepository->save($run);

        try {
            $run->markRunning(1);
            $this->seedRunRepository->save($run);

            $this->seedRunner->run($payload);

            $run->markSuccess();
            $this->seedRunRepository->save($run);
        } catch (\Throwable $e) {
            $run->markFailed($e, 1);

            try {
                if ($this->seedRunRepository->getEntityManager()->isOpen()) {
                    $this->seedRunRepository->save($run);
                }
            } catch (\Throwable) {
            }

            throw $e;
        }

        if (!$output->isQuiet()) {
            $output->writeln('<info>Seed executed successfully.</info>');
        }

        return Command::SUCCESS;
    }

    private function validateOptions(InputInterface $input, OutputInterface $output): ?int
    {
        // If user explicitly asked for --help/-h, let Symfony handle it normally.
        if ((bool) $input->getOption('help')) {
            return null;
        }

        // No actionable options => show usage
        // No actionable options => show usage (unless --full)
        if ($this->hasNoActionableOption($input) && !(bool) $input->getOption('full')) {
            if (!$output->isQuiet()) {
                $output->writeln('<comment>No option provided.</comment>');
                $output->writeln('');
                $this->renderUsage($output);
            }

            return Command::SUCCESS;
        }

        // Validate --seeder
        $only = $this->stringOrNull($input->getOption('seeder'));
        if (null !== $only && !$this->seederExists($only)) {
            if (!$output->isQuiet()) {
                $output->writeln(\sprintf('<error>Unknown seeder "%s".</error>', $only));
                $output->writeln('');
                $this->renderUsage($output);
            }

            return Command::INVALID;
        }

        return null;
    }

    private function hasNoActionableOption(InputInterface $input): bool
    {
        // Options that mean "do something"
        $actionOptions = [
            'tag',
            'seeder',
        ];

        foreach ($actionOptions as $name) {
            $value = $input->getOption($name);

            // VALUE_NONE
            if (\is_bool($value) && true === $value) {
                return false;
            }

            // VALUE_OPTIONAL
            if (\is_string($value) && '' !== $value) {
                return false;
            }
        }

        return true;
    }

    private function seederExists(string $index): bool
    {
        if (null === $this->seederIndexMap) {
            $this->seederIndexMap = [];
            foreach ($this->seeders as $seeder) {
                $this->seederIndexMap[SeederMetadata::fromSeeder($seeder)->index] = true;
            }
        }

        return isset($this->seederIndexMap[$index]);
    }

    private function renderUsage(OutputInterface $output): void
    {
        $output->writeln(\sprintf(
            "<info>Usage:</info>\n  %s\n",
            $this->getSynopsis(true)
        ));

        $output->writeln('<info>Examples:</info>');
        $output->writeln(\sprintf('  %s --list', $this->getName()));
        $output->writeln(\sprintf('  %s --seeder=core ', $this->getName()));
        $output->writeln('');

        $output->writeln('<info>Options:</info>');
        foreach ($this->getDefinition()->getOptions() as $option) {
            $name = '--'.$option->getName();
            $shortcut = $option->getShortcut() ? '-'.$option->getShortcut().', ' : '';
            $acceptValue = $option->acceptValue();
            $valueHint = $acceptValue ? '=...' : '';
            $desc = $option->getDescription();

            $output->writeln(\sprintf(
                "  <comment>%s%s%s</comment>\n      %s",
                $shortcut,
                $name,
                $valueHint,
                $desc
            ));
        }
    }

    private function stringOrNull(mixed $value): ?string
    {
        return \is_string($value) && '' !== $value ? $value : null;
    }
}
