<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Infrastructure\Api\Processor;

use ApiPlatform\Metadata\Delete;
use App\Administration\Application\Command\UnlinkParentFromStudent\UnlinkParentFromStudentHandler;
use App\Administration\Domain\Model\StudentGuardian\RelationshipType;
use App\Administration\Domain\Model\StudentGuardian\StudentGuardian;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Infrastructure\Api\Processor\UnlinkParentFromStudentProcessor;
use App\Administration\Infrastructure\Api\Resource\StudentGuardianResource;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryStudentGuardianRepository;
use App\Administration\Infrastructure\Security\StudentGuardianVoter;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use App\Shared\Infrastructure\Tenant\TenantConfig;
use App\Shared\Infrastructure\Tenant\TenantContext;
use App\Shared\Infrastructure\Tenant\TenantId as InfraTenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

final class UnlinkParentFromStudentProcessorTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440002';
    private const string SUBDOMAIN = 'ecole-alpha';
    private const string STUDENT_ID = '550e8400-e29b-41d4-a716-446655440010';
    private const string GUARDIAN_ID = '550e8400-e29b-41d4-a716-446655440020';

    private InMemoryStudentGuardianRepository $repository;
    private TenantContext $tenantContext;

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

        $this->tenantContext = new TenantContext();
        $this->tenantContext->setCurrentTenant(new TenantConfig(
            tenantId: InfraTenantId::fromString(self::TENANT_ID),
            subdomain: self::SUBDOMAIN,
            databaseUrl: 'postgresql://test',
        ));
    }

    #[Test]
    public function unlinksParentFromStudentSuccessfully(): void
    {
        $link = StudentGuardian::lier(
            studentId: UserId::fromString(self::STUDENT_ID),
            guardianId: UserId::fromString(self::GUARDIAN_ID),
            relationshipType: RelationshipType::FATHER,
            tenantId: TenantId::fromString(self::TENANT_ID),
            createdAt: new DateTimeImmutable('2026-02-10 10:00:00'),
        );
        $this->repository->save($link);

        $processor = $this->createProcessor();

        $result = $processor->process(
            new StudentGuardianResource(),
            new Delete(),
            ['studentId' => self::STUDENT_ID, 'guardianId' => self::GUARDIAN_ID],
        );

        self::assertNull($result);
    }

    #[Test]
    public function throwsAccessDeniedWhenNotAuthorized(): void
    {
        $authChecker = $this->createMock(AuthorizationCheckerInterface::class);
        $authChecker->method('isGranted')
            ->with(StudentGuardianVoter::MANAGE)
            ->willReturn(false);

        $processor = $this->createProcessor(authorizationChecker: $authChecker);

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

        $processor->process(
            new StudentGuardianResource(),
            new Delete(),
            ['studentId' => self::STUDENT_ID, 'guardianId' => self::GUARDIAN_ID],
        );
    }

    #[Test]
    public function throwsUnauthorizedWhenNoTenant(): void
    {
        $tenantContext = new TenantContext();

        $processor = $this->createProcessor(tenantContext: $tenantContext);

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

        $processor->process(
            new StudentGuardianResource(),
            new Delete(),
            ['studentId' => self::STUDENT_ID, 'guardianId' => self::GUARDIAN_ID],
        );
    }

    #[Test]
    public function throwsNotFoundWhenLinkDoesNotExist(): void
    {
        $processor = $this->createProcessor();

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

        $processor->process(
            new StudentGuardianResource(),
            new Delete(),
            ['studentId' => self::STUDENT_ID, 'guardianId' => self::GUARDIAN_ID],
        );
    }

    private function createProcessor(
        ?TenantContext $tenantContext = null,
        ?AuthorizationCheckerInterface $authorizationChecker = null,
        ?MessageBusInterface $eventBus = null,
    ): UnlinkParentFromStudentProcessor {
        $tenantContext ??= $this->tenantContext;

        if ($authorizationChecker === null) {
            $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class);
            $authorizationChecker->method('isGranted')
                ->with(StudentGuardianVoter::MANAGE)
                ->willReturn(true);
        }

        if ($eventBus === null) {
            $eventBus = $this->createMock(MessageBusInterface::class);
            $eventBus->method('dispatch')->willReturnCallback(
                static fn (object $message) => new Envelope($message),
            );
        }

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

        return new UnlinkParentFromStudentProcessor(
            $handler,
            $this->repository,
            $tenantContext,
            $authorizationChecker,
            $eventBus,
        );
    }
}
