<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Domain\Model\ActivationToken;

use App\Administration\Domain\Event\ActivationTokenGenerated;
use App\Administration\Domain\Event\ActivationTokenUsed;
use App\Administration\Domain\Exception\ActivationTokenAlreadyUsedException;
use App\Administration\Domain\Exception\ActivationTokenExpiredException;
use App\Administration\Domain\Model\ActivationToken\ActivationToken;
use App\Administration\Domain\Model\ActivationToken\ActivationTokenId;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class ActivationTokenTest extends TestCase
{
    private const string USER_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440002';
    private const string EMAIL = 'user@example.com';
    private const string ROLE = 'ROLE_PARENT';
    private const string SCHOOL_NAME = 'École Alpha';

    #[Test]
    public function generateCreatesTokenWithCorrectProperties(): void
    {
        $userId = self::USER_ID;
        $email = self::EMAIL;
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $role = self::ROLE;
        $schoolName = self::SCHOOL_NAME;
        $now = new DateTimeImmutable('2026-01-15 10:00:00');

        $token = ActivationToken::generate(
            userId: $userId,
            email: $email,
            tenantId: $tenantId,
            role: $role,
            schoolName: $schoolName,
            createdAt: $now,
        );

        self::assertInstanceOf(ActivationTokenId::class, $token->id);
        self::assertSame($userId, $token->userId);
        self::assertSame($email, $token->email);
        self::assertTrue($tenantId->equals($token->tenantId));
        self::assertSame($role, $token->role);
        self::assertSame($schoolName, $token->schoolName);
        self::assertEquals($now, $token->createdAt);
        self::assertFalse($token->isUsed());
    }

    #[Test]
    public function generateRecordsActivationTokenGeneratedEvent(): void
    {
        $token = $this->createToken();

        $events = $token->pullDomainEvents();

        self::assertCount(1, $events);
        self::assertInstanceOf(ActivationTokenGenerated::class, $events[0]);
    }

    #[Test]
    public function tokenValueIsUuidV7Format(): void
    {
        $token = $this->createToken();

        self::assertMatchesRegularExpression(
            '/^[a-f0-9]{8}-[a-f0-9]{4}-7[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i',
            $token->tokenValue,
        );
    }

    #[Test]
    public function expiresAtIs7DaysAfterCreation(): void
    {
        $createdAt = new DateTimeImmutable('2026-01-15 10:00:00');
        $expectedExpiration = new DateTimeImmutable('2026-01-22 10:00:00');

        $token = ActivationToken::generate(
            userId: self::USER_ID,
            email: self::EMAIL,
            tenantId: TenantId::fromString(self::TENANT_ID),
            role: self::ROLE,
            schoolName: self::SCHOOL_NAME,
            createdAt: $createdAt,
        );

        self::assertEquals($expectedExpiration, $token->expiresAt);
    }

    #[Test]
    public function isExpiredReturnsFalseWhenNotExpired(): void
    {
        $createdAt = new DateTimeImmutable('2026-01-15 10:00:00');
        $checkAt = new DateTimeImmutable('2026-01-20 10:00:00');

        $token = ActivationToken::generate(
            userId: self::USER_ID,
            email: self::EMAIL,
            tenantId: TenantId::fromString(self::TENANT_ID),
            role: self::ROLE,
            schoolName: self::SCHOOL_NAME,
            createdAt: $createdAt,
        );

        self::assertFalse($token->isExpired($checkAt));
    }

    #[Test]
    public function isExpiredReturnsTrueWhenExpired(): void
    {
        $createdAt = new DateTimeImmutable('2026-01-15 10:00:00');
        $checkAt = new DateTimeImmutable('2026-01-25 10:00:00');

        $token = ActivationToken::generate(
            userId: self::USER_ID,
            email: self::EMAIL,
            tenantId: TenantId::fromString(self::TENANT_ID),
            role: self::ROLE,
            schoolName: self::SCHOOL_NAME,
            createdAt: $createdAt,
        );

        self::assertTrue($token->isExpired($checkAt));
    }

    #[Test]
    public function isExpiredReturnsTrueAtExactExpirationMoment(): void
    {
        $createdAt = new DateTimeImmutable('2026-01-15 10:00:00');
        $checkAt = new DateTimeImmutable('2026-01-22 10:00:00');

        $token = ActivationToken::generate(
            userId: self::USER_ID,
            email: self::EMAIL,
            tenantId: TenantId::fromString(self::TENANT_ID),
            role: self::ROLE,
            schoolName: self::SCHOOL_NAME,
            createdAt: $createdAt,
        );

        self::assertTrue($token->isExpired($checkAt));
    }

    #[Test]
    public function useMarksTokenAsUsed(): void
    {
        $token = $this->createToken();
        $usedAt = new DateTimeImmutable('2026-01-16 10:00:00');

        $token->use($usedAt);

        self::assertTrue($token->isUsed());
        self::assertEquals($usedAt, $token->usedAt);
    }

    #[Test]
    public function useRecordsActivationTokenUsedEvent(): void
    {
        $token = $this->createToken();
        $token->pullDomainEvents();

        $usedAt = new DateTimeImmutable('2026-01-16 10:00:00');
        $token->use($usedAt);

        $events = $token->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(ActivationTokenUsed::class, $events[0]);
    }

    #[Test]
    public function useThrowsExceptionWhenTokenAlreadyUsed(): void
    {
        $token = $this->createToken();
        $firstUse = new DateTimeImmutable('2026-01-16 10:00:00');
        $token->use($firstUse);

        $this->expectException(ActivationTokenAlreadyUsedException::class);

        $secondUse = new DateTimeImmutable('2026-01-17 10:00:00');
        $token->use($secondUse);
    }

    #[Test]
    public function useThrowsExceptionWhenTokenExpired(): void
    {
        $createdAt = new DateTimeImmutable('2026-01-15 10:00:00');
        $usedAt = new DateTimeImmutable('2026-01-25 10:00:00');

        $token = ActivationToken::generate(
            userId: self::USER_ID,
            email: self::EMAIL,
            tenantId: TenantId::fromString(self::TENANT_ID),
            role: self::ROLE,
            schoolName: self::SCHOOL_NAME,
            createdAt: $createdAt,
        );

        $this->expectException(ActivationTokenExpiredException::class);

        $token->use($usedAt);
    }

    private function createToken(): ActivationToken
    {
        return ActivationToken::generate(
            userId: self::USER_ID,
            email: self::EMAIL,
            tenantId: TenantId::fromString(self::TENANT_ID),
            role: self::ROLE,
            schoolName: self::SCHOOL_NAME,
            createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
        );
    }
}
