<?php

declare(strict_types=1);

namespace App\Tests\Module\Core\Domain\Security;

use App\Module\Core\Domain\Entity\User;
use App\Module\Core\Domain\Exception\LastAdminProtectionException;
use App\Module\Core\Domain\Repository\UserRepositoryInterface;
use App\Module\Core\Domain\Security\AdminHeadcountGuard;
use PHPUnit\Framework\TestCase;

/**
 * Tests unitaires du gardien d'invariant AdminHeadcountGuard.
 *
 * Aucun acces base de donnees : UserRepositoryInterface est mocke.
 *
 * @internal
 */
class AdminHeadcountGuardTest extends TestCase
{
    // ---------------------------------------------------------------
    // Demote (retrait du flag admin)
    // ---------------------------------------------------------------

    /**
     * Autorise la demote quand il reste plus d'un admin (cas nominal).
     */
    public function testAllowsDemotionWhenMoreThanOneAdmin(): void
    {
        $repo = $this->createMock(UserRepositoryInterface::class);
        $repo->method('countAdmins')->willReturn(2);

        $guard = new AdminHeadcountGuard($repo);
        $user  = new User();
        $user->setUsername('alice');

        // Aucune exception ne doit etre levee
        $guard->ensureAtLeastOneAdminRemainsAfterDemotion($user);
        $this->addToAssertionCount(1);
    }

    /**
     * Bloque la demote quand il ne reste exactement qu'un admin.
     */
    public function testBlocksDemotionWhenExactlyOneAdmin(): void
    {
        $repo = $this->createMock(UserRepositoryInterface::class);
        $repo->method('countAdmins')->willReturn(1);

        $guard = new AdminHeadcountGuard($repo);
        $user  = new User();
        $user->setUsername('alice');

        $this->expectException(LastAdminProtectionException::class);
        $guard->ensureAtLeastOneAdminRemainsAfterDemotion($user);
    }

    /**
     * Bloque la demote de facon defensive si le compteur est a 0 (etat incoherent).
     */
    public function testBlocksDemotionDefensivelyWhenZeroAdmin(): void
    {
        $repo = $this->createMock(UserRepositoryInterface::class);
        $repo->method('countAdmins')->willReturn(0);

        $guard = new AdminHeadcountGuard($repo);
        $user  = new User();
        $user->setUsername('alice');

        $this->expectException(LastAdminProtectionException::class);
        $guard->ensureAtLeastOneAdminRemainsAfterDemotion($user);
    }

    // ---------------------------------------------------------------
    // Deletion (suppression de l'utilisateur)
    // ---------------------------------------------------------------

    /**
     * Autorise la suppression quand il reste plus d'un admin (cas nominal).
     */
    public function testAllowsDeletionWhenMoreThanOneAdmin(): void
    {
        $repo = $this->createMock(UserRepositoryInterface::class);
        $repo->method('countAdmins')->willReturn(2);

        $guard = new AdminHeadcountGuard($repo);
        $user  = new User();
        $user->setUsername('bob');

        // Aucune exception ne doit etre levee
        $guard->ensureAtLeastOneAdminRemainsAfterDeletion($user);
        $this->addToAssertionCount(1);
    }

    /**
     * Bloque la suppression quand il ne reste exactement qu'un admin.
     */
    public function testBlocksDeletionWhenExactlyOneAdmin(): void
    {
        $repo = $this->createMock(UserRepositoryInterface::class);
        $repo->method('countAdmins')->willReturn(1);

        $guard = new AdminHeadcountGuard($repo);
        $user  = new User();
        $user->setUsername('bob');

        $this->expectException(LastAdminProtectionException::class);
        $guard->ensureAtLeastOneAdminRemainsAfterDeletion($user);
    }

    /**
     * Bloque la suppression de facon defensive si le compteur est a 0 (etat incoherent).
     */
    public function testBlocksDeletionDefensivelyWhenZeroAdmin(): void
    {
        $repo = $this->createMock(UserRepositoryInterface::class);
        $repo->method('countAdmins')->willReturn(0);

        $guard = new AdminHeadcountGuard($repo);
        $user  = new User();
        $user->setUsername('bob');

        $this->expectException(LastAdminProtectionException::class);
        $guard->ensureAtLeastOneAdminRemainsAfterDeletion($user);
    }
}
