<?php

declare(strict_types=1);

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

use DH\Auditor\Auditor;
use DH\Auditor\Provider\Doctrine\Configuration;
use DH\Auditor\Provider\Doctrine\DoctrineProvider;
use DH\Auditor\Provider\Doctrine\Persistence\Schema\SchemaManager;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\LockableTrait;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(name: 'audit:schema:update', description: 'Update audit tables structure')]
final class AuditSchemaUpdateCommand extends Command
{
    use LockableTrait;

    public function __construct(private readonly Auditor $auditor)
    {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('dump-sql', null, InputOption::VALUE_NONE, 'Dumps the generated SQL statements to the screen (does not execute them).')
            ->addOption('force', 'f', InputOption::VALUE_NONE, 'Causes the generated SQL statements to be physically executed against your database.')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        if (!$this->lock()) {
            $output->writeln('The command is already running in another process.');

            return Command::SUCCESS;
        }

        $io = new SymfonyStyle($input, $output);
        $dumpSql = true === $input->getOption('dump-sql');
        $force = true === $input->getOption('force');

        /** @var DoctrineProvider $provider */
        $provider = $this->auditor->getProvider(DoctrineProvider::class);
        $this->filterDisabledEntities($provider);

        $updateManager = new SchemaManager($provider);
        $sqls = $updateManager->getUpdateAuditSchemaSql();

        $count = 0;
        foreach ($sqls as $queries) {
            $count += \count($queries);
        }

        if (0 === $count) {
            $io->success('Nothing to update.');
            $this->release();

            return Command::SUCCESS;
        }

        if ($dumpSql) {
            $io->text('The following SQL statements will be executed:');
            $io->newLine();

            foreach ($sqls as $queries) {
                foreach ($queries as $sql) {
                    $io->text(\sprintf('    %s;', $sql));
                }
            }
        }

        if ($force) {
            if ($dumpSql) {
                $io->newLine();
            }

            $io->text('Updating database schema...');
            $io->newLine();

            $progressBar = new ProgressBar($output, \count($sqls));
            $progressBar->start();

            $updateManager->updateAuditSchema($sqls, static function () use ($progressBar): void {
                $progressBar->advance();
            });

            $progressBar->finish();
            $io->newLine(2);

            $pluralization = 1 === $count ? 'query was' : 'queries were';
            $io->text(\sprintf('    <info>%s</info> %s executed', $count, $pluralization));
            $io->success('Database schema updated successfully!');
        }

        if ($dumpSql || $force) {
            $this->release();

            return Command::SUCCESS;
        }

        $io->caution('This operation should not be executed in a production environment!');
        $io->text([
            \sprintf('The Schema-Tool would execute <info>"%s"</info> queries to update the database.', $count),
            'Please run the operation by passing one - or both - of the following options:',
            \sprintf('    <info>%s --force</info> to execute the command', $this->getName()),
            \sprintf('    <info>%s --dump-sql</info> to dump the SQL statements to the screen', $this->getName()),
        ]);

        $this->release();

        return Command::SUCCESS;
    }

    private function filterDisabledEntities(DoctrineProvider $provider): void
    {
        $configuration = $provider->getConfiguration();
        if (!$configuration instanceof Configuration) {
            return;
        }

        $entities = array_filter(
            $configuration->getEntities(),
            static fn (array $options): bool => !isset($options['enabled']) || true === $options['enabled']
        );

        $configuration->setEntities($entities);
    }
}
