<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Console;

use App\Administration\Domain\Model\ActivationToken\ActivationToken;
use App\Administration\Domain\Model\PasswordResetToken\PasswordResetToken;
use App\Administration\Domain\Model\User\Email;
use App\Administration\Domain\Model\User\Role;
use App\Administration\Domain\Model\User\StatutCompte;
use App\Administration\Domain\Model\User\User;
use App\Administration\Domain\Repository\ActivationTokenRepository;
use App\Administration\Domain\Repository\PasswordResetTokenRepository;
use App\Administration\Domain\Repository\UserRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use App\Shared\Infrastructure\Tenant\TenantNotFoundException;
use App\Shared\Infrastructure\Tenant\TenantRegistry;
use DateTimeImmutable;

use function sprintf;

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\Messenger\MessageBusInterface;

#[AsCommand(
    name: 'app:dev:create-test-activation-token',
    description: 'Creates a test user and activation token for development',
)]
final class CreateTestActivationTokenCommand extends Command
{
    public function __construct(
        private readonly ActivationTokenRepository $activationTokenRepository,
        private readonly PasswordResetTokenRepository $passwordResetTokenRepository,
        private readonly UserRepository $userRepository,
        private readonly TenantRegistry $tenantRegistry,
        private readonly Clock $clock,
        private readonly MessageBusInterface $eventBus,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('email', null, InputOption::VALUE_OPTIONAL, 'Email address', 'test@example.com')
            ->addOption('role', null, InputOption::VALUE_OPTIONAL, 'User role (PARENT, ELEVE, PROF, ADMIN)', 'PARENT')
            ->addOption('school', null, InputOption::VALUE_OPTIONAL, 'School name', 'École de Test')
            ->addOption('minor', null, InputOption::VALUE_NONE, 'Create a minor user (requires parental consent)')
            ->addOption('tenant', null, InputOption::VALUE_OPTIONAL, 'Tenant subdomain (ecole-alpha, ecole-beta)', 'ecole-alpha')
            ->addOption('base-url', null, InputOption::VALUE_OPTIONAL, 'Frontend base URL', 'http://localhost:5174');
    }

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

        // Interactive mode only if:
        // 1. Input is interactive (not -n flag, has TTY)
        // 2. Using all default values (no explicit options provided)
        $usingDefaults = $input->getOption('email') === 'test@example.com'
            && $input->getOption('role') === 'PARENT'
            && $input->getOption('tenant') === 'ecole-alpha';

        if ($input->isInteractive() && $usingDefaults) {
            $io->title('Création d\'un token d\'activation de test');

            /** @var string $tenantSubdomain */
            $tenantSubdomain = $io->choice(
                'Tenant (établissement)',
                ['ecole-alpha', 'ecole-beta'],
                'ecole-alpha'
            );

            /** @var string $roleChoice */
            $roleChoice = $io->choice(
                'Rôle',
                ['PARENT', 'ELEVE', 'PROF', 'ADMIN'],
                'PARENT'
            );

            $defaultEmail = match ($roleChoice) {
                'PARENT' => 'parent@test.com',
                'ELEVE' => 'eleve@test.com',
                'PROF' => 'prof@test.com',
                'ADMIN' => 'admin@test.com',
                default => 'test@example.com',
            };

            /** @var string $email */
            $email = $io->ask('Email', $defaultEmail);
            $roleInput = strtoupper($roleChoice);
            /** @var string $schoolName */
            $schoolName = $io->ask('Nom de l\'école', 'École de Test');
            $isMinor = $io->confirm('Utilisateur mineur (nécessite consentement parental) ?', false);
        } else {
            /** @var string $email */
            $email = $input->getOption('email');
            /** @var string $roleOption */
            $roleOption = $input->getOption('role');
            $roleInput = strtoupper($roleOption);
            /** @var string $schoolName */
            $schoolName = $input->getOption('school');
            $isMinor = $input->getOption('minor');
            /** @var string $tenantSubdomain */
            $tenantSubdomain = $input->getOption('tenant');
        }

        /** @var string $baseUrlOption */
        $baseUrlOption = $input->getOption('base-url');
        $baseUrl = rtrim($baseUrlOption, '/');

        // In interactive mode, replace localhost with tenant subdomain
        if ($input->isInteractive() && $usingDefaults) {
            $baseUrl = (string) preg_replace('#//localhost([:/])#', "//{$tenantSubdomain}.classeo.local\$1", $baseUrl);
        }

        // Convert short role name to full Symfony role format
        $roleName = str_starts_with($roleInput, 'ROLE_') ? $roleInput : 'ROLE_' . $roleInput;

        $role = Role::tryFrom($roleName);
        if ($role === null) {
            $validRoles = array_map(static fn (Role $r) => str_replace('ROLE_', '', $r->value), Role::cases());
            $io->error(sprintf(
                'Invalid role "%s". Valid roles: %s',
                $roleInput,
                implode(', ', $validRoles)
            ));

            return Command::FAILURE;
        }

        // Resolve tenant from subdomain
        try {
            $tenantConfig = $this->tenantRegistry->getBySubdomain($tenantSubdomain);
            $tenantId = $tenantConfig->tenantId;
        } catch (TenantNotFoundException) {
            $availableTenants = array_map(
                static fn ($config) => $config->subdomain,
                $this->tenantRegistry->getAllConfigs()
            );
            $io->error(sprintf(
                'Tenant "%s" not found. Available tenants: %s',
                $tenantSubdomain,
                implode(', ', $availableTenants)
            ));

            return Command::FAILURE;
        }

        $now = $this->clock->now();
        $emailVO = new Email($email);

        // Check if user already exists for this tenant
        $user = $this->userRepository->findByEmail($emailVO, $tenantId);

        if ($user !== null) {
            $io->note(sprintf('User "%s" already exists, reusing existing account.', $email));

            // Active users get a password reset token instead of activation
            if ($user->statut === StatutCompte::ACTIF) {
                return $this->createPasswordResetToken($user, $email, $tenantId, $tenantSubdomain, $baseUrl, $now, $io);
            }
        } else {
            $dateNaissance = $isMinor
                ? $now->modify('-13 years') // 13 ans = mineur
                : null;

            $user = User::creer(
                email: $emailVO,
                role: $role,
                tenantId: $tenantId,
                schoolName: $schoolName,
                dateNaissance: $dateNaissance,
                createdAt: $now,
            );

            $this->userRepository->save($user);
        }

        // Create activation token
        $token = ActivationToken::generate(
            userId: (string) $user->id,
            email: $email,
            tenantId: $tenantId,
            role: $role->value,
            schoolName: $schoolName,
            createdAt: $now,
        );

        $this->activationTokenRepository->save($token);

        $activationUrl = sprintf('%s/activate/%s', $baseUrl, $token->tokenValue);

        $io->success('Test activation token created successfully!');

        $io->table(
            ['Property', 'Value'],
            [
                ['User ID', (string) $user->id],
                ['Email', $email],
                ['Role', $role->value],
                ['Tenant', $tenantSubdomain],
                ['School', $schoolName],
                ['Minor', $isMinor ? 'Yes (requires parental consent)' : 'No'],
                ['Token', $token->tokenValue],
                ['Expires', $token->expiresAt->format('Y-m-d H:i:s')],
            ]
        );

        $io->writeln('');
        $io->writeln(sprintf('<info>Activation URL:</info> <href=%s>%s</>', $activationUrl, $activationUrl));
        $io->writeln('');

        return Command::SUCCESS;
    }

    private function createPasswordResetToken(
        User $user,
        string $email,
        TenantId $tenantId,
        string $tenantSubdomain,
        string $baseUrl,
        DateTimeImmutable $now,
        SymfonyStyle $io,
    ): int {
        $io->warning('Le compte est déjà actif. Création d\'un token de réinitialisation de mot de passe à la place.');

        $resetToken = PasswordResetToken::generate(
            userId: (string) $user->id,
            email: $email,
            tenantId: $tenantId,
            createdAt: $now,
        );

        $this->passwordResetTokenRepository->save($resetToken);

        foreach ($resetToken->pullDomainEvents() as $event) {
            $this->eventBus->dispatch($event);
        }

        $resetUrl = sprintf('%s/reset-password/%s', $baseUrl, $resetToken->tokenValue);

        $io->success('Password reset token created successfully!');

        $io->table(
            ['Property', 'Value'],
            [
                ['User ID', (string) $user->id],
                ['Email', $email],
                ['Role', $user->role->value],
                ['Tenant', $tenantSubdomain],
                ['Status', $user->statut->value],
                ['Token', $resetToken->tokenValue],
                ['Expires', $resetToken->expiresAt->format('Y-m-d H:i:s')],
            ]
        );

        $io->writeln('');
        $io->writeln(sprintf('<info>Reset password URL:</info> <href=%s>%s</>', $resetUrl, $resetUrl));
        $io->writeln('');

        return Command::SUCCESS;
    }
}
