<?php

declare(strict_types=1);

namespace App\Tests\Module\Core\Infrastructure\ApiPlatform\State\Processor;

use ApiPlatform\Metadata\Delete;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Core\Domain\Entity\User;
use App\Module\Core\Domain\Exception\LastAdminProtectionException;
use App\Module\Core\Domain\Security\AdminHeadcountGuardInterface;
use App\Module\Core\Infrastructure\ApiPlatform\State\Processor\UserProcessor;
use LogicException;
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use stdClass;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

/**
 * Tests unitaires du UserProcessor : couvre la garde "dernier admin global"
 * et la delegation au RemoveProcessor Doctrine decore pour l'operation DELETE.
 *
 * @internal
 */
#[AllowMockObjectsWithoutExpectations]
final class UserProcessorTest extends TestCase
{
    private MockObject&ProcessorInterface $removeProcessor;
    private AdminHeadcountGuardInterface&MockObject $adminHeadcountGuard;
    private UserProcessor $processor;

    protected function setUp(): void
    {
        $this->removeProcessor     = $this->createMock(ProcessorInterface::class);
        $this->adminHeadcountGuard = $this->createMock(AdminHeadcountGuardInterface::class);

        $this->processor = new UserProcessor(
            $this->removeProcessor,
            $this->adminHeadcountGuard,
        );
    }

    public function testDelegatesWhenUserIsNotAdmin(): void
    {
        $user = new User();
        $user->setUsername('alice');
        $user->setIsAdmin(false);

        // La garde ne doit jamais etre appellee pour un non-admin.
        $this->adminHeadcountGuard
            ->expects($this->never())
            ->method('ensureAtLeastOneAdminRemainsAfterDeletion')
        ;

        $this->removeProcessor
            ->expects($this->once())
            ->method('process')
            ->with($user)
            ->willReturn(null)
        ;

        $result = $this->processor->process($user, new Delete());

        self::assertNull($result);
    }

    public function testDelegatesWhenAdminButNotLast(): void
    {
        $user = new User();
        $user->setUsername('admin');
        $user->setIsAdmin(true);

        // La garde est appelee et ne leve pas d'exception (il reste d'autres admins).
        $this->adminHeadcountGuard
            ->expects($this->once())
            ->method('ensureAtLeastOneAdminRemainsAfterDeletion')
            ->with($user)
        ;

        $this->removeProcessor
            ->expects($this->once())
            ->method('process')
            ->with($user)
            ->willReturn(null)
        ;

        $this->processor->process($user, new Delete());
    }

    public function testBlocksWhenDeletingLastAdmin(): void
    {
        $user = new User();
        $user->setUsername('admin');
        $user->setIsAdmin(true);

        $exceptionMessage = 'Impossible : au moins un administrateur doit rester sur l\'instance.';

        $this->adminHeadcountGuard
            ->expects($this->once())
            ->method('ensureAtLeastOneAdminRemainsAfterDeletion')
            ->with($user)
            ->willThrowException(new LastAdminProtectionException($exceptionMessage))
        ;

        // La suppression ne doit pas etre executee si la garde echoue.
        $this->removeProcessor
            ->expects($this->never())
            ->method('process')
        ;

        $this->expectException(BadRequestHttpException::class);
        $this->expectExceptionMessage($exceptionMessage);

        $this->processor->process($user, new Delete());
    }

    public function testFailFastOnInvalidDataType(): void
    {
        // Garde-fou contre une misconfiguration : ce processor est wire
        // exclusivement sur l'operation Delete de User.
        $this->adminHeadcountGuard->expects($this->never())->method('ensureAtLeastOneAdminRemainsAfterDeletion');
        $this->removeProcessor->expects($this->never())->method('process');

        $this->expectException(LogicException::class);
        $this->expectExceptionMessage('UserProcessor attend une instance de');

        $this->processor->process(new stdClass(), new Delete());
    }
}
