<?php

declare(strict_types=1);

namespace App\Tests\Module\Core\Api;

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

/**
 * Tests fonctionnels de l'endpoint GET /api/me.
 *
 * Verifie que la reponse inclut `isAdmin` et `effectivePermissions`
 * dans le groupe de serialisation `me:read`.
 *
 * Strategie de donnees :
 *  - Les tests 1-3 s'appuient exclusivement sur les fixtures (admin/alice).
 *  - Le test 4 cree un user jetable prefixe `test_me_` + role + permission,
 *    purges en tearDown.
 *
 * @internal
 */
final class MeApiTest extends AbstractApiTestCase
{
    private const TEST_USER_PREFIX       = 'test_me_';
    private const TEST_ROLE_PREFIX       = 'test_me_';
    private const TEST_PERMISSION_PREFIX = 'test.me.';

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

    /**
     * L'admin (isAdmin=true, role systeme sans permission explicite) doit
     * obtenir un payload /me avec isAdmin=true et effectivePermissions=[].
     */
    public function testMeEndpointReturnsIsAdminAndEffectivePermissionsForAdmin(): void
    {
        $client   = $this->authenticatedClient('admin', 'admin');
        $response = $client->request('GET', '/api/me', [
            'headers' => ['Accept' => 'application/ld+json'],
        ]);

        self::assertResponseIsSuccessful();

        $data = $response->toArray();

        self::assertSame('admin', $data['username'], 'Le champ username doit etre "admin".');
        self::assertTrue($data['isAdmin'], 'isAdmin doit etre true pour l\'admin fixture.');
        self::assertArrayHasKey('effectivePermissions', $data, 'effectivePermissions doit etre present dans le payload.');
        self::assertIsArray($data['effectivePermissions'], 'effectivePermissions doit etre un tableau JSON.');
        // Le role systeme admin n'a pas de permissions explicites : tableau vide attendu.
        self::assertSame([], $data['effectivePermissions'], 'effectivePermissions doit etre [] pour l\'admin sans permissions explicites.');
    }

    /**
     * Un utilisateur standard (isAdmin=false, role user sans permission) doit
     * obtenir isAdmin=false et effectivePermissions=[].
     */
    public function testMeEndpointReturnsEmptyPermissionsForStandardUser(): void
    {
        $client   = $this->authenticatedClient('alice', 'alice');
        $response = $client->request('GET', '/api/me', [
            'headers' => ['Accept' => 'application/ld+json'],
        ]);

        self::assertResponseIsSuccessful();

        $data = $response->toArray();

        self::assertFalse($data['isAdmin'], 'isAdmin doit etre false pour alice.');
        self::assertArrayHasKey('effectivePermissions', $data, 'effectivePermissions doit etre present dans le payload.');
        self::assertSame([], $data['effectivePermissions'], 'effectivePermissions doit etre [] pour un user sans role avec permission.');
    }

    /**
     * Une requete non authentifiee sur /api/me doit retourner 401.
     */
    public function testMeEndpointRequiresAuthentication(): void
    {
        $client = self::createClient();
        $client->request('GET', '/api/me', [
            'headers' => ['Accept' => 'application/ld+json'],
        ]);

        self::assertResponseStatusCodeSame(401);
    }

    /**
     * Un user rattache a un role portant la permission `core.users.view` doit
     * retrouver cette permission dans effectivePermissions, triee alphabetiquement.
     */
    public function testMeEndpointReturnsEffectivePermissionsForUserWithRolePermissions(): void
    {
        // --- Preparation des donnees de test ---
        self::bootKernel();
        $em = $this->getEm();

        $this->cleanupTestData();

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

        $permission = new Permission('test.me.core.users.view', 'View users (test me)', 'core');
        $em->persist($permission);

        $role = new Role('test_me_viewer', 'Viewer (test me)', false);
        $role->addPermission($permission);
        $em->persist($role);

        $user = new User();
        $user->setUsername('test_me_viewer_user');
        $user->setIsAdmin(false);
        $user->setPassword($hasher->hashPassword($user, 'secret'));
        $user->addRbacRole($role);
        $em->persist($user);

        $em->flush();
        $em->clear();

        // --- Appel API ---
        $client   = $this->authenticatedClient('test_me_viewer_user', 'secret');
        $response = $client->request('GET', '/api/me', [
            'headers' => ['Accept' => 'application/ld+json'],
        ]);

        self::assertResponseIsSuccessful();

        $data = $response->toArray();

        self::assertArrayHasKey('effectivePermissions', $data, 'effectivePermissions doit etre present dans le payload.');
        self::assertContains(
            'test.me.core.users.view',
            $data['effectivePermissions'],
            'effectivePermissions doit contenir le code de permission du role attribue.',
        );

        // Verifie le tri alphabetique (contrat spec section 9 ticket-343).
        $sorted = $data['effectivePermissions'];
        $copy   = $sorted;
        sort($copy);
        self::assertSame($copy, $sorted, 'effectivePermissions doit etre trie alphabetiquement.');
    }

    /**
     * Purge les entites de test creees par les methodes ci-dessus.
     * Ordre : users d'abord (FK vers roles), puis roles, puis permissions.
     */
    private function cleanupTestData(): void
    {
        $em = $this->getEm();

        $em->createQuery(
            'DELETE FROM '.User::class.' u WHERE u.username LIKE :prefix'
        )->setParameter('prefix', self::TEST_USER_PREFIX.'%')->execute();

        $em->createQuery(
            'DELETE FROM '.Role::class.' r WHERE r.code LIKE :prefix'
        )->setParameter('prefix', self::TEST_ROLE_PREFIX.'%')->execute();

        $em->createQuery(
            'DELETE FROM '.Permission::class.' p WHERE p.code LIKE :prefix'
        )->setParameter('prefix', self::TEST_PERMISSION_PREFIX.'%')->execute();
    }
}
