<?php

declare(strict_types=1);

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

use App\Administration\Domain\Event\InvitationParentEnvoyee;
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\Role;
use App\Administration\Domain\Model\User\User;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Infrastructure\Messaging\SendParentInvitationEmailHandler;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryParentInvitationRepository;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryUserRepository;
use App\Shared\Domain\Tenant\TenantId;
use App\Shared\Infrastructure\Tenant\TenantConfig;
use App\Shared\Infrastructure\Tenant\TenantId as InfraTenantId;
use App\Shared\Infrastructure\Tenant\TenantRegistry;
use App\Shared\Infrastructure\Tenant\TenantUrlBuilder;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email as MimeEmail;
use Twig\Environment;

final class SendParentInvitationEmailHandlerTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440002';
    private const string FROM_EMAIL = 'noreply@classeo.fr';

    private InMemoryParentInvitationRepository $invitationRepository;
    private InMemoryUserRepository $userRepository;
    private TenantUrlBuilder $tenantUrlBuilder;

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

        $tenantConfig = new TenantConfig(
            tenantId: InfraTenantId::fromString(self::TENANT_ID),
            subdomain: 'ecole-alpha',
            databaseUrl: 'sqlite:///:memory:',
        );

        $tenantRegistry = $this->createMock(TenantRegistry::class);
        $tenantRegistry->method('getConfig')->willReturn($tenantConfig);

        $this->tenantUrlBuilder = new TenantUrlBuilder(
            $tenantRegistry,
            'https://classeo.fr',
            'classeo.fr',
        );
    }

    #[Test]
    public function itSendsParentInvitationEmailWithStudentName(): void
    {
        $student = $this->createAndSaveStudent('Alice', 'Dupont');
        $invitation = $this->createAndSaveInvitation($student->id, 'parent@example.com');

        $mailer = $this->createMock(MailerInterface::class);
        $twig = $this->createMock(Environment::class);

        $twig->expects($this->once())
            ->method('render')
            ->with('emails/parent_invitation.html.twig', $this->callback(
                static fn (array $params): bool => $params['studentName'] === 'Alice Dupont'
                    && str_contains($params['activationUrl'], 'ecole-alpha.classeo.fr/parent-activate/'),
            ))
            ->willReturn('<html>parent invitation</html>');

        $mailer->expects($this->once())
            ->method('send')
            ->with($this->callback(
                static fn (MimeEmail $email): bool => $email->getTo()[0]->getAddress() === 'parent@example.com'
                    && $email->getSubject() === 'Invitation à rejoindre Classeo'
                    && $email->getHtmlBody() === '<html>parent invitation</html>',
            ));

        $handler = new SendParentInvitationEmailHandler(
            $mailer,
            $twig,
            $this->invitationRepository,
            $this->userRepository,
            $this->tenantUrlBuilder,
            self::FROM_EMAIL,
        );

        $event = new InvitationParentEnvoyee(
            invitationId: $invitation->id,
            studentId: $student->id,
            parentEmail: $invitation->parentEmail,
            tenantId: $invitation->tenantId,
            occurredOn: new DateTimeImmutable('2026-02-07 10:00:00'),
        );

        ($handler)($event);
    }

    #[Test]
    public function itSendsFromConfiguredEmailAddress(): void
    {
        $student = $this->createAndSaveStudent('Bob', 'Martin');
        $invitation = $this->createAndSaveInvitation($student->id, 'parent2@example.com');

        $mailer = $this->createMock(MailerInterface::class);
        $twig = $this->createMock(Environment::class);
        $twig->method('render')->willReturn('<html>invitation</html>');

        $customFrom = 'custom@school.fr';

        $mailer->expects($this->once())
            ->method('send')
            ->with($this->callback(
                static fn (MimeEmail $email): bool => $email->getFrom()[0]->getAddress() === $customFrom,
            ));

        $handler = new SendParentInvitationEmailHandler(
            $mailer,
            $twig,
            $this->invitationRepository,
            $this->userRepository,
            $this->tenantUrlBuilder,
            $customFrom,
        );

        $event = new InvitationParentEnvoyee(
            invitationId: $invitation->id,
            studentId: $student->id,
            parentEmail: $invitation->parentEmail,
            tenantId: $invitation->tenantId,
            occurredOn: new DateTimeImmutable('2026-02-07 10:00:00'),
        );

        ($handler)($event);
    }

    #[Test]
    public function itDoesNothingWhenInvitationNotFound(): void
    {
        $student = $this->createAndSaveStudent('Charlie', 'Durand');

        $mailer = $this->createMock(MailerInterface::class);
        $twig = $this->createMock(Environment::class);

        $mailer->expects($this->never())->method('send');

        $handler = new SendParentInvitationEmailHandler(
            $mailer,
            $twig,
            $this->invitationRepository,
            $this->userRepository,
            $this->tenantUrlBuilder,
            self::FROM_EMAIL,
        );

        // Event with a non-existent invitation ID
        $event = new InvitationParentEnvoyee(
            invitationId: \App\Administration\Domain\Model\Invitation\ParentInvitationId::generate(),
            studentId: $student->id,
            parentEmail: new Email('ghost@example.com'),
            tenantId: TenantId::fromString(self::TENANT_ID),
            occurredOn: new DateTimeImmutable('2026-02-07 10:00:00'),
        );

        ($handler)($event);
    }

    private function createAndSaveStudent(string $firstName, string $lastName): User
    {
        $student = User::inviter(
            email: new Email($firstName . '@example.com'),
            role: Role::ELEVE,
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolName: 'École Alpha',
            firstName: $firstName,
            lastName: $lastName,
            invitedAt: new DateTimeImmutable('2026-02-07 10:00:00'),
        );
        $student->pullDomainEvents();
        $this->userRepository->save($student);

        return $student;
    }

    private function createAndSaveInvitation(UserId $studentId, string $parentEmail): ParentInvitation
    {
        $invitation = ParentInvitation::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            studentId: $studentId,
            parentEmail: new Email($parentEmail),
            code: new InvitationCode(str_repeat('a', 32)),
            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->invitationRepository->save($invitation);

        return $invitation;
    }
}
