<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Application\Command\ActivateParentInvitation;

use App\Administration\Application\Command\ActivateParentInvitation\ActivateParentInvitationCommand;
use App\Administration\Application\Command\ActivateParentInvitation\ActivateParentInvitationHandler;
use App\Administration\Application\Port\PasswordHasher;
use App\Administration\Domain\Exception\InvitationDejaActiveeException;
use App\Administration\Domain\Exception\InvitationExpireeException;
use App\Administration\Domain\Exception\InvitationNonEnvoyeeException;
use App\Administration\Domain\Exception\ParentInvitationNotFoundException;
use App\Administration\Domain\Model\Invitation\InvitationCode;
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\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;

final class ActivateParentInvitationHandlerTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440002';
    private const string CODE = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';

    private InMemoryParentInvitationRepository $repository;
    private ActivateParentInvitationHandler $handler;

    protected function setUp(): void
    {
        $this->repository = new InMemoryParentInvitationRepository();

        $clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-02-08 10:00:00');
            }
        };

        $passwordHasher = new class implements PasswordHasher {
            public function hash(string $plainPassword): string
            {
                return 'hashed_' . $plainPassword;
            }

            public function verify(string $hashedPassword, string $plainPassword): bool
            {
                return $hashedPassword === 'hashed_' . $plainPassword;
            }
        };

        $this->handler = new ActivateParentInvitationHandler(
            $this->repository,
            $passwordHasher,
            $clock,
        );
    }

    #[Test]
    public function itValidatesAndReturnsActivationResult(): void
    {
        $invitation = $this->createSentInvitation();

        $result = ($this->handler)(new ActivateParentInvitationCommand(
            code: self::CODE,
            firstName: 'Jean',
            lastName: 'Parent',
            password: 'SecurePass123!',
        ));

        self::assertSame((string) $invitation->id, $result->invitationId);
        self::assertSame((string) $invitation->studentId, $result->studentId);
        self::assertSame('parent@example.com', $result->parentEmail);
        self::assertSame('hashed_SecurePass123!', $result->hashedPassword);
        self::assertSame('Jean', $result->firstName);
        self::assertSame('Parent', $result->lastName);
    }

    #[Test]
    public function itThrowsWhenCodeNotFound(): void
    {
        $this->expectException(ParentInvitationNotFoundException::class);

        ($this->handler)(new ActivateParentInvitationCommand(
            code: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
            firstName: 'Jean',
            lastName: 'Parent',
            password: 'SecurePass123!',
        ));
    }

    #[Test]
    public function itThrowsWhenInvitationNotSent(): void
    {
        $this->createPendingInvitation();

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

        ($this->handler)(new ActivateParentInvitationCommand(
            code: self::CODE,
            firstName: 'Jean',
            lastName: 'Parent',
            password: 'SecurePass123!',
        ));
    }

    #[Test]
    public function itThrowsWhenInvitationExpired(): void
    {
        $this->createExpiredInvitation();

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

        ($this->handler)(new ActivateParentInvitationCommand(
            code: self::CODE,
            firstName: 'Jean',
            lastName: 'Parent',
            password: 'SecurePass123!',
        ));
    }

    #[Test]
    public function itThrowsWhenInvitationAlreadyActivated(): void
    {
        $this->createActivatedInvitation();

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

        ($this->handler)(new ActivateParentInvitationCommand(
            code: self::CODE,
            firstName: 'Jean',
            lastName: 'Parent',
            password: 'SecurePass123!',
        ));
    }

    private function createSentInvitation(): ParentInvitation
    {
        $invitation = ParentInvitation::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            studentId: UserId::generate(),
            parentEmail: new Email('parent@example.com'),
            code: new InvitationCode(self::CODE),
            createdAt: new DateTimeImmutable('2026-02-07 10:00:00'),
            createdBy: UserId::generate(),
        );
        $invitation->envoyer(new DateTimeImmutable('2026-02-07 10:00:00'));
        $invitation->pullDomainEvents();
        $this->repository->save($invitation);

        return $invitation;
    }

    private function createPendingInvitation(): ParentInvitation
    {
        $invitation = ParentInvitation::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            studentId: UserId::generate(),
            parentEmail: new Email('parent@example.com'),
            code: new InvitationCode(self::CODE),
            createdAt: new DateTimeImmutable('2026-02-07 10:00:00'),
            createdBy: UserId::generate(),
        );
        $invitation->pullDomainEvents();
        $this->repository->save($invitation);

        return $invitation;
    }

    private function createExpiredInvitation(): ParentInvitation
    {
        $invitation = ParentInvitation::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            studentId: UserId::generate(),
            parentEmail: new Email('parent@example.com'),
            code: new InvitationCode(self::CODE),
            createdAt: new DateTimeImmutable('2026-01-01 10:00:00'),
            createdBy: UserId::generate(),
        );
        $invitation->envoyer(new DateTimeImmutable('2026-01-01 10:00:00'));
        $invitation->pullDomainEvents();
        $this->repository->save($invitation);

        return $invitation;
    }

    private function createActivatedInvitation(): ParentInvitation
    {
        $invitation = ParentInvitation::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            studentId: UserId::generate(),
            parentEmail: new Email('parent@example.com'),
            code: new InvitationCode(self::CODE),
            createdAt: new DateTimeImmutable('2026-02-07 10:00:00'),
            createdBy: UserId::generate(),
        );
        $invitation->envoyer(new DateTimeImmutable('2026-02-07 10:00:00'));
        $invitation->activer(UserId::generate(), new DateTimeImmutable('2026-02-07 12:00:00'));
        $invitation->pullDomainEvents();
        $this->repository->save($invitation);

        return $invitation;
    }
}
