<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Infrastructure\Console;

use App\Administration\Domain\Event\PasswordResetTokenGenerated;
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\Model\User\UserId;
use App\Administration\Infrastructure\Console\CreateTestActivationTokenCommand;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryActivationTokenRepository;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryPasswordResetTokenRepository;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryUserRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use App\Shared\Infrastructure\Tenant\InMemoryTenantRegistry;
use App\Shared\Infrastructure\Tenant\TenantConfig;
use App\Shared\Infrastructure\Tenant\TenantId as InfraTenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;

final class CreateTestActivationTokenCommandTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440002';
    private const string SUBDOMAIN = 'ecole-alpha';

    private InMemoryActivationTokenRepository $activationTokenRepository;
    private InMemoryPasswordResetTokenRepository $passwordResetTokenRepository;
    private InMemoryUserRepository $userRepository;
    private InMemoryTenantRegistry $tenantRegistry;
    private Clock $clock;
    /** @var list<object> */
    private array $dispatchedEvents = [];
    private MessageBusInterface $eventBus;
    private CommandTester $commandTester;

    protected function setUp(): void
    {
        $this->activationTokenRepository = new InMemoryActivationTokenRepository();
        $this->passwordResetTokenRepository = new InMemoryPasswordResetTokenRepository();
        $this->userRepository = new InMemoryUserRepository();
        $this->clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-02-08 10:00:00');
            }
        };

        $tenantId = InfraTenantId::fromString(self::TENANT_ID);
        $this->tenantRegistry = new InMemoryTenantRegistry([
            new TenantConfig($tenantId, self::SUBDOMAIN, 'postgresql://localhost/test'),
        ]);

        $dispatchedEvents = &$this->dispatchedEvents;
        $this->eventBus = new class($dispatchedEvents) implements MessageBusInterface {
            /** @param list<object> $events */
            public function __construct(private array &$events)
            {
            }

            public function dispatch(object $message, array $stamps = []): Envelope
            {
                $this->events[] = $message;

                return new Envelope($message);
            }
        };

        $command = new CreateTestActivationTokenCommand(
            $this->activationTokenRepository,
            $this->passwordResetTokenRepository,
            $this->userRepository,
            $this->tenantRegistry,
            $this->clock,
            $this->eventBus,
        );

        $this->commandTester = new CommandTester($command);
    }

    #[Test]
    public function itCreatesActivationTokenForNewUser(): void
    {
        $this->commandTester->execute([
            '--email' => 'new@test.com',
            '--role' => 'PARENT',
            '--tenant' => self::SUBDOMAIN,
        ]);

        self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode());

        $output = $this->commandTester->getDisplay();
        self::assertStringContainsString('Test activation token created successfully', $output);
        self::assertStringContainsString('Activation URL', $output);
    }

    #[Test]
    public function itCreatesActivationTokenForExistingPendingUser(): void
    {
        $user = User::creer(
            email: new Email('pending@test.com'),
            role: Role::PARENT,
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolName: 'École Test',
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
        );
        $this->userRepository->save($user);

        $this->commandTester->execute([
            '--email' => 'pending@test.com',
            '--role' => 'PARENT',
            '--tenant' => self::SUBDOMAIN,
        ]);

        self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode());

        $output = $this->commandTester->getDisplay();
        self::assertStringContainsString('already exists', $output);
        self::assertStringContainsString('Activation URL', $output);
        self::assertStringNotContainsString('Reset password URL', $output);
    }

    #[Test]
    public function itCreatesPasswordResetTokenForActiveUser(): void
    {
        $user = $this->createActiveUser('active@test.com');
        $this->userRepository->save($user);

        $this->commandTester->execute([
            '--email' => 'active@test.com',
            '--role' => 'PARENT',
            '--tenant' => self::SUBDOMAIN,
        ]);

        self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode());

        $output = $this->commandTester->getDisplay();
        self::assertStringContainsString('déjà actif', $output);
        self::assertStringContainsString('Password reset token created successfully', $output);
        self::assertStringContainsString('Reset password URL', $output);
        self::assertStringNotContainsString('Activation URL', $output);
    }

    #[Test]
    public function itDispatchesPasswordResetEventForActiveUser(): void
    {
        $user = $this->createActiveUser('active@test.com');
        $this->userRepository->save($user);

        $this->commandTester->execute([
            '--email' => 'active@test.com',
            '--role' => 'PARENT',
            '--tenant' => self::SUBDOMAIN,
        ]);

        self::assertCount(1, $this->dispatchedEvents);
        self::assertInstanceOf(PasswordResetTokenGenerated::class, $this->dispatchedEvents[0]);
        self::assertSame('active@test.com', $this->dispatchedEvents[0]->email);
    }

    #[Test]
    public function itDoesNotDispatchEventsForNewUser(): void
    {
        $this->commandTester->execute([
            '--email' => 'new@test.com',
            '--role' => 'PARENT',
            '--tenant' => self::SUBDOMAIN,
        ]);

        self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode());
        self::assertCount(0, $this->dispatchedEvents);
    }

    private function createActiveUser(string $email): User
    {
        return User::reconstitute(
            id: UserId::generate(),
            email: new Email($email),
            roles: [Role::PARENT],
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolName: 'École Test',
            statut: StatutCompte::ACTIF,
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
            hashedPassword: '$argon2id$hashed',
            activatedAt: new DateTimeImmutable('2026-01-16 10:00:00'),
            consentementParental: null,
        );
    }
}
