<?php

declare(strict_types=1);

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

use App\Administration\Domain\Model\Invitation\InvitationCode;
use App\Administration\Domain\Model\Invitation\InvitationStatus;
use App\Administration\Domain\Model\Invitation\ParentInvitation;
use App\Administration\Domain\Model\User\Email;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Infrastructure\Console\ExpireInvitationsCommand;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryParentInvitationRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\Console\Tester\CommandTester;

final class ExpireInvitationsCommandTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string STUDENT_ID = '550e8400-e29b-41d4-a716-446655440002';
    private const string CREATED_BY_ID = '550e8400-e29b-41d4-a716-446655440003';

    private InMemoryParentInvitationRepository $repository;
    private Clock $clock;

    protected function setUp(): void
    {
        $this->repository = new InMemoryParentInvitationRepository();
        $this->clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-02-28 10:00:00');
            }
        };
    }

    #[Test]
    public function itExpiresInvitationsPastExpirationDate(): void
    {
        $invitation = $this->creerInvitation(
            createdAt: new DateTimeImmutable('2026-02-01 10:00:00'),
            code: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4',
        );
        $invitation->envoyer(new DateTimeImmutable('2026-02-01 11:00:00'));
        $this->repository->save($invitation);

        $tester = $this->executeCommand();

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('1 invitation(s) expirée(s) trouvée(s)', $tester->getDisplay());
        self::assertStringContainsString('1 invitation(s) marquée(s) comme expirée(s)', $tester->getDisplay());
        self::assertSame(InvitationStatus::EXPIRED, $invitation->status);
    }

    #[Test]
    public function itHandlesNoExpiredInvitations(): void
    {
        $tester = $this->executeCommand();

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('Aucune invitation expirée à traiter.', $tester->getDisplay());
    }

    #[Test]
    public function itDoesNotExpirePendingInvitations(): void
    {
        $invitation = $this->creerInvitation(
            createdAt: new DateTimeImmutable('2026-02-01 10:00:00'),
            code: 'b1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4',
        );
        $this->repository->save($invitation);

        $tester = $this->executeCommand();

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('Aucune invitation expirée à traiter.', $tester->getDisplay());
        self::assertSame(InvitationStatus::PENDING, $invitation->status);
    }

    #[Test]
    public function itDoesNotExpireNonExpiredSentInvitations(): void
    {
        $invitation = $this->creerInvitation(
            createdAt: new DateTimeImmutable('2026-02-25 10:00:00'),
            code: 'c1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4',
        );
        $invitation->envoyer(new DateTimeImmutable('2026-02-25 11:00:00'));
        $this->repository->save($invitation);

        $tester = $this->executeCommand();

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('Aucune invitation expirée à traiter.', $tester->getDisplay());
        self::assertSame(InvitationStatus::SENT, $invitation->status);
    }

    private function executeCommand(): CommandTester
    {
        $command = new ExpireInvitationsCommand(
            $this->repository,
            $this->clock,
            new NullLogger(),
        );

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

        return $tester;
    }

    private function creerInvitation(
        DateTimeImmutable $createdAt,
        string $code,
    ): ParentInvitation {
        return ParentInvitation::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            studentId: UserId::fromString(self::STUDENT_ID),
            parentEmail: new Email('parent@example.com'),
            code: new InvitationCode($code),
            createdAt: $createdAt,
            createdBy: UserId::fromString(self::CREATED_BY_ID),
        );
    }
}
