<?php

namespace App\Tests\Controller\Api;

use App\Controller\Api\ApiAuthTrait;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

class ApiAuthTraitTest extends TestCase
{
    private const SECRET = 'test_secret_for_trait';

    private function createConsumer(): object
    {
        return new class {
            use ApiAuthTrait;

            public function doAuth(Request $request, EntityManagerInterface $em, string $appSecret): User|JsonResponse
            {
                return $this->authenticateRequest($request, $em, $appSecret);
            }

            public function doSuccess(mixed $data, array $meta = []): JsonResponse
            {
                return $this->success($data, $meta);
            }

            public function doError(string $message, int $status = 400): JsonResponse
            {
                return $this->error($message, $status);
            }
        };
    }

    private function generateToken(array $overrides = []): string
    {
        $header = $this->b64(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
        $payload = array_merge([
            'userId' => 1,
            'email' => 'orga@test.com',
            'roles' => ['ROLE_ORGANIZER'],
            'iat' => time(),
            'exp' => time() + 86400,
        ], $overrides);
        $payloadB64 = $this->b64(json_encode($payload));
        $sig = $this->b64(hash_hmac('sha256', $header.'.'.$payloadB64, self::SECRET, true));

        return $header.'.'.$payloadB64.'.'.$sig;
    }

    private function b64(string $data): string
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    private function mockEm(?User $user = null): EntityManagerInterface
    {
        $repo = $this->createMock(EntityRepository::class);
        $repo->method('find')->willReturn($user);

        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn($repo);

        return $em;
    }

    // --- authenticateRequest ---

    public function testAuthMissingHeaders(): void
    {
        $consumer = $this->createConsumer();
        $request = Request::create('/api/test');
        $result = $consumer->doAuth($request, $this->mockEm(), self::SECRET);

        self::assertInstanceOf(JsonResponse::class, $result);
        self::assertSame(401, $result->getStatusCode());
        self::assertStringContainsString('headers manquants', $result->getContent());
    }

    public function testAuthInvalidToken(): void
    {
        $consumer = $this->createConsumer();
        $request = Request::create('/api/test');
        $request->headers->set('ETicket-Email', 'orga@test.com');
        $request->headers->set('ETicket-JWT', 'invalid.token.here');
        $result = $consumer->doAuth($request, $this->mockEm(), self::SECRET);

        self::assertInstanceOf(JsonResponse::class, $result);
        self::assertSame(401, $result->getStatusCode());
    }

    public function testAuthExpiredToken(): void
    {
        $consumer = $this->createConsumer();
        $token = $this->generateToken(['exp' => time() - 100]);
        $request = Request::create('/api/test');
        $request->headers->set('ETicket-Email', 'orga@test.com');
        $request->headers->set('ETicket-JWT', $token);
        $result = $consumer->doAuth($request, $this->mockEm(), self::SECRET);

        self::assertInstanceOf(JsonResponse::class, $result);
        self::assertSame(401, $result->getStatusCode());
        self::assertStringContainsString('expire', $result->getContent());
    }

    public function testAuthUserNotFound(): void
    {
        $consumer = $this->createConsumer();
        $token = $this->generateToken();
        $request = Request::create('/api/test');
        $request->headers->set('ETicket-Email', 'orga@test.com');
        $request->headers->set('ETicket-JWT', $token);
        $result = $consumer->doAuth($request, $this->mockEm(null), self::SECRET);

        self::assertInstanceOf(JsonResponse::class, $result);
        self::assertSame(401, $result->getStatusCode());
        self::assertStringContainsString('introuvable', $result->getContent());
    }

    public function testAuthEmailMismatch(): void
    {
        $consumer = $this->createConsumer();
        $token = $this->generateToken();
        $user = $this->createMock(User::class);
        $user->method('getEmail')->willReturn('other@test.com');
        $request = Request::create('/api/test');
        $request->headers->set('ETicket-Email', 'orga@test.com');
        $request->headers->set('ETicket-JWT', $token);
        $result = $consumer->doAuth($request, $this->mockEm($user), self::SECRET);

        self::assertInstanceOf(JsonResponse::class, $result);
        self::assertSame(401, $result->getStatusCode());
    }

    public function testAuthSuccess(): void
    {
        $consumer = $this->createConsumer();
        $token = $this->generateToken();
        $user = $this->createMock(User::class);
        $user->method('getEmail')->willReturn('orga@test.com');
        $request = Request::create('/api/test');
        $request->headers->set('ETicket-Email', 'orga@test.com');
        $request->headers->set('ETicket-JWT', $token);
        $result = $consumer->doAuth($request, $this->mockEm($user), self::SECRET);

        self::assertInstanceOf(User::class, $result);
    }

    // --- success ---

    public function testSuccessWithoutMeta(): void
    {
        $consumer = $this->createConsumer();
        $response = $consumer->doSuccess(['id' => 1]);
        $data = json_decode($response->getContent(), true);

        self::assertSame(200, $response->getStatusCode());
        self::assertTrue($data['success']);
        self::assertSame(['id' => 1], $data['data']);
        self::assertNull($data['error']);
        self::assertArrayNotHasKey('meta', $data);
    }

    public function testSuccessWithMeta(): void
    {
        $consumer = $this->createConsumer();
        $response = $consumer->doSuccess([], ['page' => 1, 'total' => 5]);
        $data = json_decode($response->getContent(), true);

        self::assertSame(['page' => 1, 'total' => 5], $data['meta']);
    }

    // --- error ---

    public function testError(): void
    {
        $consumer = $this->createConsumer();
        $response = $consumer->doError('Something failed', 422);
        $data = json_decode($response->getContent(), true);

        self::assertSame(422, $response->getStatusCode());
        self::assertFalse($data['success']);
        self::assertNull($data['data']);
        self::assertSame('Something failed', $data['error']);
    }

    public function testErrorDefaultStatus(): void
    {
        $consumer = $this->createConsumer();
        $response = $consumer->doError('Bad request');

        self::assertSame(400, $response->getStatusCode());
    }
}
