<?php

declare(strict_types=1);

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

use App\Administration\Domain\Model\User\Email;
use App\Administration\Domain\Model\User\Role;
use App\Administration\Domain\Model\User\User;
use App\Administration\Infrastructure\Console\MigrateUsersToPostgresCommand;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryUserRepository;
use App\Shared\Infrastructure\Tenant\TenantConfig;
use App\Shared\Infrastructure\Tenant\TenantId;
use App\Shared\Infrastructure\Tenant\TenantRegistry;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use Symfony\Component\Console\Tester\CommandTester;

final class MigrateUsersToPostgresCommandTest extends TestCase
{
    private const string TENANT_ALPHA_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
    private const string TENANT_BETA_ID = 'b2c3d4e5-f6a7-8901-bcde-f12345678901';

    #[Test]
    public function migratesUsersFromSourceToTarget(): void
    {
        $source = new InMemoryUserRepository();
        $target = new InMemoryUserRepository();

        $tenantAlpha = TenantId::fromString(self::TENANT_ALPHA_ID);

        $user1 = $this->createUser('user1@example.com', $tenantAlpha);
        $user2 = $this->createUser('user2@example.com', $tenantAlpha);
        $source->save($user1);
        $source->save($user2);

        $registry = $this->createRegistry([
            new TenantConfig($tenantAlpha, 'ecole-alpha', 'sqlite:///:memory:'),
        ]);

        $command = new MigrateUsersToPostgresCommand($source, $target, $registry);
        $tester = new CommandTester($command);
        $tester->execute([]);

        self::assertSame(0, $tester->getStatusCode());
        self::assertCount(2, $target->findAllByTenant($tenantAlpha));
        self::assertStringContainsString('2 utilisateur(s) migré(s)', $tester->getDisplay());
    }

    #[Test]
    public function isIdempotent(): void
    {
        $source = new InMemoryUserRepository();
        $target = new InMemoryUserRepository();

        $tenantAlpha = TenantId::fromString(self::TENANT_ALPHA_ID);
        $user = $this->createUser('user@example.com', $tenantAlpha);
        $source->save($user);

        $registry = $this->createRegistry([
            new TenantConfig($tenantAlpha, 'ecole-alpha', 'sqlite:///:memory:'),
        ]);

        $command = new MigrateUsersToPostgresCommand($source, $target, $registry);

        // Run twice
        $tester = new CommandTester($command);
        $tester->execute([]);
        $tester->execute([]);

        // Should still have exactly 1 user, not 2
        self::assertCount(1, $target->findAllByTenant($tenantAlpha));
    }

    #[Test]
    public function handlesMultipleTenants(): void
    {
        $source = new InMemoryUserRepository();
        $target = new InMemoryUserRepository();

        $tenantAlpha = TenantId::fromString(self::TENANT_ALPHA_ID);
        $tenantBeta = TenantId::fromString(self::TENANT_BETA_ID);

        $source->save($this->createUser('alpha@example.com', $tenantAlpha));
        $source->save($this->createUser('beta@example.com', $tenantBeta));

        $registry = $this->createRegistry([
            new TenantConfig($tenantAlpha, 'ecole-alpha', 'sqlite:///:memory:'),
            new TenantConfig($tenantBeta, 'ecole-beta', 'sqlite:///:memory:'),
        ]);

        $command = new MigrateUsersToPostgresCommand($source, $target, $registry);
        $tester = new CommandTester($command);
        $tester->execute([]);

        self::assertSame(0, $tester->getStatusCode());
        self::assertCount(1, $target->findAllByTenant($tenantAlpha));
        self::assertCount(1, $target->findAllByTenant($tenantBeta));
    }

    #[Test]
    public function handlesEmptyTenant(): void
    {
        $source = new InMemoryUserRepository();
        $target = new InMemoryUserRepository();

        $tenantAlpha = TenantId::fromString(self::TENANT_ALPHA_ID);

        $registry = $this->createRegistry([
            new TenantConfig($tenantAlpha, 'ecole-alpha', 'sqlite:///:memory:'),
        ]);

        $command = new MigrateUsersToPostgresCommand($source, $target, $registry);
        $tester = new CommandTester($command);
        $tester->execute([]);

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('aucun utilisateur', $tester->getDisplay());
    }

    #[Test]
    public function preservesUserIds(): void
    {
        $source = new InMemoryUserRepository();
        $target = new InMemoryUserRepository();

        $tenantAlpha = TenantId::fromString(self::TENANT_ALPHA_ID);
        $user = $this->createUser('user@example.com', $tenantAlpha);
        $originalId = (string) $user->id;
        $source->save($user);

        $registry = $this->createRegistry([
            new TenantConfig($tenantAlpha, 'ecole-alpha', 'sqlite:///:memory:'),
        ]);

        $command = new MigrateUsersToPostgresCommand($source, $target, $registry);
        $tester = new CommandTester($command);
        $tester->execute([]);

        $migratedUsers = $target->findAllByTenant($tenantAlpha);
        self::assertSame($originalId, (string) $migratedUsers[0]->id);
    }

    private function createUser(string $email, TenantId $tenantId): User
    {
        return User::creer(
            email: new Email($email),
            role: Role::PARENT,
            tenantId: $tenantId,
            schoolName: 'École Test',
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15T10:00:00+00:00'),
        );
    }

    /**
     * @param TenantConfig[] $configs
     */
    private function createRegistry(array $configs): TenantRegistry
    {
        return new class($configs) implements TenantRegistry {
            /** @param TenantConfig[] $configs */
            public function __construct(private readonly array $configs)
            {
            }

            public function getConfig(TenantId $tenantId): TenantConfig
            {
                foreach ($this->configs as $config) {
                    if ($config->tenantId->equals($tenantId)) {
                        return $config;
                    }
                }

                throw new RuntimeException('Tenant not found');
            }

            public function getBySubdomain(string $subdomain): TenantConfig
            {
                foreach ($this->configs as $config) {
                    if ($config->subdomain === $subdomain) {
                        return $config;
                    }
                }

                throw new RuntimeException('Tenant not found');
            }

            public function exists(string $subdomain): bool
            {
                foreach ($this->configs as $config) {
                    if ($config->subdomain === $subdomain) {
                        return true;
                    }
                }

                return false;
            }

            /** @return TenantConfig[] */
            public function getAllConfigs(): array
            {
                return $this->configs;
            }
        };
    }
}
