<?php

declare(strict_types=1);

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

use App\Administration\Application\Command\LinkParentToStudent\LinkParentToStudentCommand;
use App\Administration\Application\Command\LinkParentToStudent\LinkParentToStudentHandler;
use App\Administration\Domain\Exception\InvalidGuardianRoleException;
use App\Administration\Domain\Exception\InvalidStudentRoleException;
use App\Administration\Domain\Exception\LiaisonDejaExistanteException;
use App\Administration\Domain\Exception\MaxGuardiansReachedException;
use App\Administration\Domain\Exception\TenantMismatchException;
use App\Administration\Domain\Model\StudentGuardian\RelationshipType;
use App\Administration\Domain\Model\StudentGuardian\StudentGuardian;
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\Domain\Repository\UserRepository;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryStudentGuardianRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class LinkParentToStudentHandlerTest 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 GUARDIAN_ID = '550e8400-e29b-41d4-a716-446655440003';
    private const string GUARDIAN_2_ID = '550e8400-e29b-41d4-a716-446655440004';
    private const string GUARDIAN_3_ID = '550e8400-e29b-41d4-a716-446655440005';
    private const string ADMIN_ID = '550e8400-e29b-41d4-a716-446655440006';
    private const string OTHER_TENANT_ID = '550e8400-e29b-41d4-a716-446655440099';

    private InMemoryStudentGuardianRepository $repository;

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

    private function createHandlerWithMockedUsers(
        ?Role $guardianRole = null,
        ?Role $studentRole = null,
        ?string $guardianTenantId = null,
        ?string $studentTenantId = null,
    ): LinkParentToStudentHandler {
        $guardianRole ??= Role::PARENT;
        $studentRole ??= Role::ELEVE;

        $now = new DateTimeImmutable('2026-02-10 10:00:00');

        $guardianUser = User::creer(
            email: new Email('guardian@example.com'),
            role: $guardianRole,
            tenantId: TenantId::fromString($guardianTenantId ?? self::TENANT_ID),
            schoolName: 'École Test',
            dateNaissance: null,
            createdAt: $now,
        );

        $studentUser = User::creer(
            email: new Email('student@example.com'),
            role: $studentRole,
            tenantId: TenantId::fromString($studentTenantId ?? self::TENANT_ID),
            schoolName: 'École Test',
            dateNaissance: null,
            createdAt: $now,
        );

        $userRepository = $this->createMock(UserRepository::class);
        $userRepository->method('get')->willReturnCallback(
            static function (UserId $id) use ($guardianUser, $studentUser): User {
                if ((string) $id === self::GUARDIAN_ID || (string) $id === self::GUARDIAN_2_ID || (string) $id === self::GUARDIAN_3_ID) {
                    return $guardianUser;
                }

                return $studentUser;
            },
        );

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

        return new LinkParentToStudentHandler($this->repository, $userRepository, $clock);
    }

    #[Test]
    public function linkParentToStudentSuccessfully(): void
    {
        $handler = $this->createHandlerWithMockedUsers();

        $command = new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_ID,
            relationshipType: RelationshipType::FATHER->value,
            tenantId: self::TENANT_ID,
            createdBy: self::ADMIN_ID,
        );

        $link = ($handler)($command);

        self::assertInstanceOf(StudentGuardian::class, $link);
        self::assertSame(RelationshipType::FATHER, $link->relationshipType);
        self::assertNotNull($link->createdBy);
    }

    #[Test]
    public function linkIsSavedToRepository(): void
    {
        $handler = $this->createHandlerWithMockedUsers();

        $command = new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_ID,
            relationshipType: RelationshipType::MOTHER->value,
            tenantId: self::TENANT_ID,
        );

        $link = ($handler)($command);

        $saved = $this->repository->get($link->id, TenantId::fromString(self::TENANT_ID));
        self::assertTrue($saved->id->equals($link->id));
    }

    #[Test]
    public function throwsWhenLinkAlreadyExists(): void
    {
        $handler = $this->createHandlerWithMockedUsers();

        $command = new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_ID,
            relationshipType: RelationshipType::FATHER->value,
            tenantId: self::TENANT_ID,
        );

        ($handler)($command);

        $this->expectException(LiaisonDejaExistanteException::class);
        ($handler)($command);
    }

    #[Test]
    public function throwsWhenMaxGuardiansReached(): void
    {
        $handler = $this->createHandlerWithMockedUsers();

        ($handler)(new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_ID,
            relationshipType: RelationshipType::FATHER->value,
            tenantId: self::TENANT_ID,
        ));

        ($handler)(new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_2_ID,
            relationshipType: RelationshipType::MOTHER->value,
            tenantId: self::TENANT_ID,
        ));

        $this->expectException(MaxGuardiansReachedException::class);
        ($handler)(new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_3_ID,
            relationshipType: RelationshipType::TUTOR_M->value,
            tenantId: self::TENANT_ID,
        ));
    }

    #[Test]
    public function allowsTwoGuardiansForSameStudent(): void
    {
        $handler = $this->createHandlerWithMockedUsers();

        ($handler)(new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_ID,
            relationshipType: RelationshipType::FATHER->value,
            tenantId: self::TENANT_ID,
        ));

        $link2 = ($handler)(new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_2_ID,
            relationshipType: RelationshipType::MOTHER->value,
            tenantId: self::TENANT_ID,
        ));

        self::assertInstanceOf(StudentGuardian::class, $link2);
        self::assertSame(2, $this->repository->countGuardiansForStudent(
            $link2->studentId,
            TenantId::fromString(self::TENANT_ID),
        ));
    }

    #[Test]
    public function linkWithoutCreatedByAllowsNull(): void
    {
        $handler = $this->createHandlerWithMockedUsers();

        $command = new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_ID,
            relationshipType: RelationshipType::FATHER->value,
            tenantId: self::TENANT_ID,
        );

        $link = ($handler)($command);

        self::assertNull($link->createdBy);
    }

    #[Test]
    public function throwsWhenGuardianIsNotParent(): void
    {
        $handler = $this->createHandlerWithMockedUsers(guardianRole: Role::ELEVE);

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

        ($handler)(new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_ID,
            relationshipType: RelationshipType::FATHER->value,
            tenantId: self::TENANT_ID,
        ));
    }

    #[Test]
    public function throwsWhenStudentIsNotEleve(): void
    {
        $handler = $this->createHandlerWithMockedUsers(studentRole: Role::PARENT);

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

        ($handler)(new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_ID,
            relationshipType: RelationshipType::FATHER->value,
            tenantId: self::TENANT_ID,
        ));
    }

    #[Test]
    public function throwsWhenGuardianBelongsToDifferentTenant(): void
    {
        $handler = $this->createHandlerWithMockedUsers(guardianTenantId: self::OTHER_TENANT_ID);

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

        ($handler)(new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_ID,
            relationshipType: RelationshipType::FATHER->value,
            tenantId: self::TENANT_ID,
        ));
    }

    #[Test]
    public function throwsWhenStudentBelongsToDifferentTenant(): void
    {
        $handler = $this->createHandlerWithMockedUsers(studentTenantId: self::OTHER_TENANT_ID);

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

        ($handler)(new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_ID,
            relationshipType: RelationshipType::FATHER->value,
            tenantId: self::TENANT_ID,
        ));
    }

    #[Test]
    public function throwsWhenRelationshipTypeIsInvalid(): void
    {
        $handler = $this->createHandlerWithMockedUsers();

        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('Type de relation invalide');

        ($handler)(new LinkParentToStudentCommand(
            studentId: self::STUDENT_ID,
            guardianId: self::GUARDIAN_ID,
            relationshipType: 'invalide',
            tenantId: self::TENANT_ID,
        ));
    }
}
