<?php

namespace App\Tests\Unit\Controller;

use App\Controller\PushController;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\RateLimiter\Storage\InMemoryStorage;

class PushControllerTest extends TestCase
{
    private EntityManagerInterface $em;

    protected function setUp(): void
    {
        $this->em = $this->createStub(EntityManagerInterface::class);
    }

    private function makeController(bool $exhaust = false, ?EntityManagerInterface $em = null): PushController
    {
        $storage = new InMemoryStorage();
        $factory = new RateLimiterFactory([
            'id'     => 'push_test',
            'policy' => 'token_bucket',
            'limit'  => 5,
            'rate'   => ['interval' => '1 minute'],
        ], $storage);

        if ($exhaust) {
            $limiter = $factory->create('1.2.3.4');
            for ($i = 0; $i < 6; $i++) {
                $limiter->consume();
            }
        }

        $controller = new PushController($em ?? $this->em, $factory, 'fake-vapid-public-key');

        $container = $this->createStub(ContainerInterface::class);
        $container->method('has')->willReturn(false);
        $controller->setContainer($container);

        return $controller;
    }

    private function makeRequest(string $method, string $body, string $ip = '1.2.3.4'): Request
    {
        $request = Request::create('/api/push/subscribe', $method, [], [], [], [], $body);
        $request->server->set('REMOTE_ADDR', $ip);
        return $request;
    }

    // ── subscribe ─────────────────────────────────────────────────────────────

    public function testSubscribeBodyTooLargeReturns413(): void
    {
        $body = json_encode([
            'endpoint' => 'https://push.example.com/sub',
            'keys'     => ['auth' => 'a', 'p256dh' => 'b'],
            'pad'      => str_repeat('x', 4100),
        ]);
        self::assertSame(413, $this->controller()->subscribe($this->makeRequest('POST', $body))->getStatusCode());
    }

    public function testSubscribeMissingFieldsReturns422(): void
    {
        self::assertSame(422, $this->controller()->subscribe(
            $this->makeRequest('POST', json_encode(['endpoint' => 'https://push.example.com']))
        )->getStatusCode());
    }

    public function testSubscribeInvalidEndpointUrlReturns422(): void
    {
        $body = json_encode(['endpoint' => 'not-a-url', 'keys' => ['auth' => 'a', 'p256dh' => 'b']]);
        self::assertSame(422, $this->controller()->subscribe($this->makeRequest('POST', $body))->getStatusCode());
    }

    public function testSubscribeValidRequestReturns201(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->method('getRepository')->willReturn(
            $this->createConfiguredStub(\Doctrine\ORM\EntityRepository::class, ['findOneBy' => null])
        );
        $em->expects(self::once())->method('persist');
        $em->expects(self::once())->method('flush');

        $body = json_encode([
            'endpoint' => 'https://fcm.googleapis.com/fcm/send/abc123',
            'keys'     => ['auth' => 'authtoken', 'p256dh' => 'p256key'],
        ]);
        self::assertSame(201, $this->makeController(em: $em)->subscribe($this->makeRequest('POST', $body))->getStatusCode());
    }

    public function testSubscribeRateLimitExceededReturns429(): void
    {
        self::assertSame(429, $this->makeController(exhaust: true)->subscribe(
            $this->makeRequest('POST', json_encode([
                'endpoint' => 'https://push.example.com/sub',
                'keys'     => ['auth' => 'a', 'p256dh' => 'b'],
            ]))
        )->getStatusCode());
    }

    // ── unsubscribe ───────────────────────────────────────────────────────────

    public function testUnsubscribeMissingEndpointReturns422(): void
    {
        self::assertSame(422, $this->controller()->unsubscribe(
            $this->makeRequest('DELETE', json_encode([]))
        )->getStatusCode());
    }

    public function testUnsubscribeUnknownEndpointReturns204(): void
    {
        $this->em->method('getRepository')->willReturn(
            $this->createConfiguredStub(\Doctrine\ORM\EntityRepository::class, ['findOneBy' => null])
        );

        self::assertSame(204, $this->controller()->unsubscribe(
            $this->makeRequest('DELETE', json_encode(['endpoint' => 'https://push.example.com/sub']))
        )->getStatusCode());
    }

    public function testUnsubscribeRateLimitExceededReturns429(): void
    {
        self::assertSame(429, $this->makeController(exhaust: true)->unsubscribe(
            $this->makeRequest('DELETE', json_encode(['endpoint' => 'https://push.example.com/sub']))
        )->getStatusCode());
    }

    private function controller(): PushController
    {
        return $this->makeController();
    }
}
