<?php

declare(strict_types=1);

namespace App\Tests\Module\Core\Api;

use App\Module\Core\Domain\Entity\Role;
use App\Module\Core\Domain\Entity\User;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

/**
 * Tests fonctionnels de l'exposition API Platform de l'entite User.
 *
 * Strategie :
 *  - Les fixtures chargent 3 users : admin (is_admin=true), alice, bob.
 *  - Les tests de lecture s'appuient sur les fixtures sans les modifier.
 *  - Les tests de suppression et de guard "dernier admin" creent des users
 *    additionnels via EntityManager, purges en tearDown.
 *  - On ne supprime JAMAIS les users fixture (admin / alice / bob).
 *
 * @internal
 */
final class UserApiTest extends AbstractApiTestCase
{
    private const TEST_USER_PREFIX = 'test_';
    private const TEST_ROLE_PREFIX = 'test_';

    protected function tearDown(): void
    {
        $this->cleanupTestData();
        parent::tearDown();
    }

    // --- Tests lecture collection ---

    public function testListUsersAsAdminReturns200(): void
    {
        $client   = $this->authenticatedClient('admin', 'admin');
        $response = $client->request('GET', '/api/users');

        self::assertResponseIsSuccessful();
        $data = $response->toArray();
        self::assertArrayHasKey('member', $data);
        // Au moins 3 users fixture.
        self::assertGreaterThanOrEqual(3, $data['totalItems']);
    }

    public function testListUsersAsUserWithViewPermissionReturns200(): void
    {
        // Un non-admin portant core.users.view doit pouvoir lister les users.
        $credentials = $this->createUserWithPermission('core.users.view');
        $client      = $this->authenticatedClient($credentials['username'], $credentials['password']);
        $client->request('GET', '/api/users');

        self::assertResponseIsSuccessful();
    }

    public function testListUsersAsStandardUserReturns403(): void
    {
        // alice n'a aucune permission RBAC : acces refuse.
        $client = $this->authenticatedClient('alice', 'alice');
        $client->request('GET', '/api/users');

        self::assertResponseStatusCodeSame(403);
    }

    // --- Tests suppression ---

    public function testDeleteNonAdminUserAsAdminReturns204(): void
    {
        // Confirme que la suppression d'un user non-admin fonctionne.
        $em = $this->getEm();

        /** @var UserPasswordHasherInterface $hasher */
        $hasher = self::getContainer()->get(UserPasswordHasherInterface::class);

        $target = new User();
        $target->setUsername('test_deletable_user');
        $target->setIsAdmin(false);
        $target->setPassword($hasher->hashPassword($target, 'secret'));
        $em->persist($target);
        $em->flush();
        $targetId = $target->getId();
        $em->clear();

        $client = $this->authenticatedClient('admin', 'admin');
        $client->request('DELETE', '/api/users/'.$targetId);

        self::assertResponseStatusCodeSame(204);

        // Verification cote base : le user n'existe plus.
        $em = $this->getEm();
        $em->clear();
        self::assertNull($em->getRepository(User::class)->find($targetId));
    }

    public function testDeleteSecondAdminReturns204(): void
    {
        // Quand il y a 2 admins, supprimer le second est autorise (garde non declenchee).
        $em = $this->getEm();

        /** @var UserPasswordHasherInterface $hasher */
        $hasher = self::getContainer()->get(UserPasswordHasherInterface::class);

        $secondAdmin = new User();
        $secondAdmin->setUsername('test_second_admin');
        $secondAdmin->setIsAdmin(true);
        $secondAdmin->setPassword($hasher->hashPassword($secondAdmin, 'secret'));
        $em->persist($secondAdmin);
        $em->flush();
        $secondAdminId = $secondAdmin->getId();
        $em->clear();

        // Auth en tant qu'admin fixture, supprime le second admin.
        $client = $this->authenticatedClient('admin', 'admin');
        $client->request('DELETE', '/api/users/'.$secondAdminId);

        self::assertResponseStatusCodeSame(204);

        $em = $this->getEm();
        $em->clear();
        self::assertNull($em->getRepository(User::class)->find($secondAdminId));
    }

    public function testDeleteLastAdminReturns400(): void
    {
        // Scenario "dernier admin global" : un seul admin existe (fixture admin).
        // Il tente de se supprimer lui-meme -> garde activee -> 400.
        $em = $this->getEm();

        /** @var null|User $fixtureAdmin */
        $fixtureAdmin = $em->getRepository(User::class)->findOneBy(['username' => 'admin']);
        self::assertNotNull($fixtureAdmin, 'L\'user admin fixture doit exister.');
        $fixtureAdminId = $fixtureAdmin->getId();

        // Garantit qu'il n'y a qu'un seul admin au moment du test :
        // s'assure que test_second_admin n'existe pas (tearDown le purge, mais
        // soyons defensifs si un test precedent n'a pas nettoye).
        $em->createQuery(
            'DELETE FROM '.User::class.' u WHERE u.username LIKE :prefix AND u.username != :admin'
        )->setParameters(['prefix' => 'test_%', 'admin' => 'admin'])->execute();

        // Auth en tant que l'admin fixture et tente l'auto-suppression.
        $client   = $this->authenticatedClient('admin', 'admin');
        $response = $client->request('DELETE', '/api/users/'.$fixtureAdminId);

        self::assertResponseStatusCodeSame(400);

        // Verification cote base : l'admin fixture doit toujours exister.
        $em = $this->getEm();
        $em->clear();
        self::assertNotNull(
            $em->getRepository(User::class)->find($fixtureAdminId),
            'Le dernier admin ne doit PAS etre supprime.',
        );
    }

    public function testDeleteAsStandardUserReturns403(): void
    {
        $em = $this->getEm();

        /** @var null|User $alice */
        $alice = $em->getRepository(User::class)->findOneBy(['username' => 'alice']);
        self::assertNotNull($alice);

        /** @var null|User $bob */
        $bob = $em->getRepository(User::class)->findOneBy(['username' => 'bob']);
        self::assertNotNull($bob);

        // alice sans permission ne peut pas supprimer bob.
        $client = $this->authenticatedClient('alice', 'alice');
        $client->request('DELETE', '/api/users/'.$bob->getId());

        self::assertResponseStatusCodeSame(403);
    }

    /**
     * Purge les entites de test creees par cette suite.
     * Ne touche JAMAIS aux fixtures (admin / alice / bob).
     */
    private function cleanupTestData(): void
    {
        $em = $this->getEm();

        // Purge des users jetables crees par les tests (y compris testuser_ de createUserWithPermission).
        $em->createQuery(
            'DELETE FROM '.User::class.' u WHERE u.username LIKE :prefix'
        )->setParameter('prefix', self::TEST_USER_PREFIX.'%')->execute();

        // Purge des roles jetables crees par createUserWithPermission.
        $em->createQuery(
            'DELETE FROM '.Role::class.' r WHERE r.code LIKE :prefix'
        )->setParameter('prefix', self::TEST_ROLE_PREFIX.'%')->execute();
    }
}
