<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Console;

use App\Administration\Domain\Model\AcademicYear\PeriodType;
use App\Administration\Domain\Model\SchoolCalendar\SchoolZone;
use App\Administration\Infrastructure\Service\DemoDataGenerationResult;
use App\Administration\Infrastructure\Service\DemoDataGenerator;
use App\Shared\Infrastructure\Tenant\TenantConfig;
use App\Shared\Infrastructure\Tenant\TenantNotFoundException;
use App\Shared\Infrastructure\Tenant\TenantRegistry;

use function array_map;
use function count;
use function getenv;
use function implode;

use Override;

use function sprintf;
use function strtolower;
use function strtoupper;

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\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Process\Process;
use Throwable;

use function trim;

#[AsCommand(
    name: 'app:dev:generate-demo-data',
    description: 'Génère un jeu de données complet pour un tenant de démonstration',
)]
final class GenerateDemoDataCommand extends Command
{
    public function __construct(
        private readonly TenantRegistry $tenantRegistry,
        private readonly DemoDataGenerator $demoDataGenerator,
        #[Autowire('%kernel.project_dir%')]
        private readonly string $projectDir,
    ) {
        parent::__construct();
    }

    #[Override]
    protected function configure(): void
    {
        $this
            ->addOption('tenant', null, InputOption::VALUE_OPTIONAL, 'Sous-domaine du tenant cible')
            ->addOption('password', null, InputOption::VALUE_OPTIONAL, 'Mot de passe partagé pour tous les comptes de démo', 'DemoPassword123!')
            ->addOption('school', null, InputOption::VALUE_OPTIONAL, 'Nom de l’établissement affiché sur les comptes générés')
            ->addOption('zone', null, InputOption::VALUE_OPTIONAL, 'Zone scolaire (A, B ou C)', 'B')
            ->addOption('period-type', null, InputOption::VALUE_OPTIONAL, 'Découpage des périodes (trimester ou semester)', PeriodType::TRIMESTER->value)
            ->addOption('internal-run', null, InputOption::VALUE_NONE, 'Option interne pour exécuter la génération dans la base tenant');
    }

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

        /** @var string|null $tenantOption */
        $tenantOption = $input->getOption('tenant');
        /** @var string $password */
        $password = $input->getOption('password');
        /** @var string|null $schoolOption */
        $schoolOption = $input->getOption('school');
        /** @var string $zoneOption */
        $zoneOption = $input->getOption('zone');
        /** @var string $periodTypeOption */
        $periodTypeOption = $input->getOption('period-type');
        $internalRun = $input->getOption('internal-run');

        $tenantConfig = $this->resolveTenantConfig($tenantOption, $io);
        if ($tenantConfig === null) {
            return Command::FAILURE;
        }

        $zone = SchoolZone::tryFrom(strtoupper(trim($zoneOption)));
        if ($zone === null) {
            $io->error('Zone invalide. Valeurs attendues: A, B, C.');

            return Command::FAILURE;
        }

        $periodType = PeriodType::tryFrom(strtolower(trim($periodTypeOption)));
        if ($periodType === null) {
            $io->error('Type de période invalide. Valeurs attendues: trimester, semester.');

            return Command::FAILURE;
        }

        $schoolName = trim((string) $schoolOption);
        if ($schoolName === '') {
            $schoolName = $this->demoDataGenerator->defaultSchoolNameForSubdomain($tenantConfig->subdomain);
        }

        if ($internalRun) {
            return $this->runInTenantProcess($tenantConfig, $password, $schoolName, $zone, $periodType, $io);
        }

        return $this->relaunchAgainstTenantDatabase($tenantConfig, $password, $schoolName, $zone, $periodType, $io);
    }

    private function relaunchAgainstTenantDatabase(
        TenantConfig $tenantConfig,
        string $password,
        string $schoolName,
        SchoolZone $zone,
        PeriodType $periodType,
        SymfonyStyle $io,
    ): int {
        $io->section(sprintf(
            'Génération du jeu de démo pour le tenant "%s" sur sa base dédiée',
            $tenantConfig->subdomain,
        ));

        $process = new Process(
            command: [
                'php',
                'bin/console',
                'app:dev:generate-demo-data',
                '--tenant=' . $tenantConfig->subdomain,
                '--password=' . $password,
                '--school=' . $schoolName,
                '--zone=' . $zone->value,
                '--period-type=' . $periodType->value,
                '--internal-run',
            ],
            cwd: $this->projectDir,
            env: [
                ...getenv(),
                'DATABASE_URL' => $tenantConfig->databaseUrl,
            ],
            timeout: 300,
        );

        $process->run(static function (string $type, string $buffer) use ($io): void {
            $io->write($buffer);
        });

        if ($process->isSuccessful()) {
            return Command::SUCCESS;
        }

        $io->error(sprintf(
            'La génération a échoué pour le tenant "%s".',
            $tenantConfig->subdomain,
        ));

        return Command::FAILURE;
    }

    private function runInTenantProcess(
        TenantConfig $tenantConfig,
        string $password,
        string $schoolName,
        SchoolZone $zone,
        PeriodType $periodType,
        SymfonyStyle $io,
    ): int {
        try {
            $result = $this->demoDataGenerator->generate(
                tenantConfig: $tenantConfig,
                password: $password,
                schoolName: $schoolName,
                zone: $zone,
                periodType: $periodType,
            );
        } catch (Throwable $e) {
            $io->error([
                sprintf('Impossible de générer les données de démo pour "%s".', $tenantConfig->subdomain),
                $e->getMessage(),
            ]);

            return Command::FAILURE;
        }

        $this->renderResult($result, $io);

        return Command::SUCCESS;
    }

    private function renderResult(DemoDataGenerationResult $result, SymfonyStyle $io): void
    {
        $io->success(sprintf(
            'Jeu de démo prêt pour le tenant "%s" (%s).',
            $result->tenantSubdomain,
            $result->academicYearLabel,
        ));

        $io->table(
            ['Élément', 'Résultat'],
            [
                ['Établissement', $result->schoolName],
                ['Utilisateurs créés', (string) $result->createdUsers],
                ['Matières créées', (string) $result->createdSubjects],
                ['Classes créées', (string) $result->createdClasses],
                ['Affectations élèves', (string) $result->createdClassAssignments],
                ['Affectations enseignants', (string) $result->createdTeacherAssignments],
                ['Liens parent-enfant', (string) $result->createdGuardianLinks],
                ['Créneaux d’emploi du temps', (string) $result->createdScheduleSlots],
                ['Périodes créées', $result->periodConfigurationCreated ? 'oui' : 'non'],
                ['Calendrier créé', $result->schoolCalendarCreated ? 'oui' : 'non'],
                ['Mode de notation créé', $result->gradingConfigurationCreated ? 'oui' : 'non'],
            ],
        );

        $io->writeln(sprintf('Mot de passe commun: <info>%s</info>', $result->sharedPassword));

        $io->table(
            ['Rôle', 'Nom', 'Email'],
            array_map(
                static fn (array $account): array => [
                    $account['role'],
                    $account['name'],
                    $account['email'],
                ],
                $result->accounts,
            ),
        );

        if ($result->warnings !== []) {
            $io->warning($result->warnings);
        }
    }

    private function resolveTenantConfig(?string $tenantOption, SymfonyStyle $io): ?TenantConfig
    {
        $tenant = trim((string) $tenantOption);

        if ($tenant !== '') {
            try {
                return $this->tenantRegistry->getBySubdomain($tenant);
            } catch (TenantNotFoundException) {
                $io->error(sprintf(
                    'Tenant "%s" introuvable. Disponibles: %s',
                    $tenant,
                    implode(', ', $this->availableSubdomains()),
                ));

                return null;
            }
        }

        $configs = $this->tenantRegistry->getAllConfigs();
        if (count($configs) === 1) {
            return $configs[0];
        }

        $io->error(sprintf(
            'Plusieurs tenants sont configurés. Précise --tenant parmi: %s',
            implode(', ', $this->availableSubdomains()),
        ));

        return null;
    }

    /**
     * @return list<string>
     */
    private function availableSubdomains(): array
    {
        return array_values(array_map(
            static fn (TenantConfig $config): string => $config->subdomain,
            $this->tenantRegistry->getAllConfigs(),
        ));
    }
}
