<?php

namespace App\Tests\Unit\Controller;

use App\Controller\FeedbackController;
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 FeedbackControllerTest extends TestCase
{
    private EntityManagerInterface $em;

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

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

        if ($exhaust) {
            // Vider les tokens avec la même clé que le controller (l'IP du request)
            $limiter = $factory->create('1.2.3.4');
            for ($i = 0; $i < 11; $i++) {
                $limiter->consume();
            }
        }

        $controller = new FeedbackController($em ?? $this->em, $factory);

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

        return $controller;
    }

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

    // ── Taille de payload ─────────────────────────────────────────────────────

    public function testBodyTooLargeReturns413(): void
    {
        $body = json_encode(['lat' => 14.65, 'lng' => -61.0, 'hasSeaweed' => true, 'pad' => str_repeat('x', 4100)]);
        self::assertSame(413, $this->makeController()->create($this->makeRequest($body))->getStatusCode());
    }

    // ── Validation JSON ───────────────────────────────────────────────────────

    public function testInvalidJsonReturns400(): void
    {
        self::assertSame(400, $this->makeController()->create($this->makeRequest('not-json'))->getStatusCode());
    }

    // ── Champs obligatoires ───────────────────────────────────────────────────

    public function testMissingLatReturns422(): void
    {
        self::assertSame(422, $this->makeController()->create(
            $this->makeRequest(json_encode(['lng' => -61.0, 'hasSeaweed' => true]))
        )->getStatusCode());
    }

    public function testMissingLngReturns422(): void
    {
        self::assertSame(422, $this->makeController()->create(
            $this->makeRequest(json_encode(['lat' => 14.65, 'hasSeaweed' => true]))
        )->getStatusCode());
    }

    public function testMissingHasSeaweedReturns422(): void
    {
        self::assertSame(422, $this->makeController()->create(
            $this->makeRequest(json_encode(['lat' => 14.65, 'lng' => -61.0]))
        )->getStatusCode());
    }

    // ── Validation lat/lng ────────────────────────────────────────────────────

    public function testInvalidLatStringReturns422(): void
    {
        self::assertSame(422, $this->makeController()->create(
            $this->makeRequest(json_encode(['lat' => 'abc', 'lng' => -61.0, 'hasSeaweed' => true]))
        )->getStatusCode());
    }

    public function testLatAboveRangeReturns422(): void
    {
        self::assertSame(422, $this->makeController()->create(
            $this->makeRequest(json_encode(['lat' => 91.0, 'lng' => -61.0, 'hasSeaweed' => true]))
        )->getStatusCode());
    }

    public function testLatBelowRangeReturns422(): void
    {
        self::assertSame(422, $this->makeController()->create(
            $this->makeRequest(json_encode(['lat' => -91.0, 'lng' => -61.0, 'hasSeaweed' => true]))
        )->getStatusCode());
    }

    public function testLngAboveRangeReturns422(): void
    {
        self::assertSame(422, $this->makeController()->create(
            $this->makeRequest(json_encode(['lat' => 14.65, 'lng' => 181.0, 'hasSeaweed' => true]))
        )->getStatusCode());
    }

    // ── Frontières exactes ────────────────────────────────────────────────────

    public function testExactBoundaryLatLngIsAccepted(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects(self::once())->method('persist');
        $em->expects(self::once())->method('flush');

        self::assertSame(201, $this->makeController(em: $em)->create(
            $this->makeRequest(json_encode(['lat' => 90.0, 'lng' => -180.0, 'hasSeaweed' => false]))
        )->getStatusCode());
    }

    // ── Requête valide ────────────────────────────────────────────────────────

    public function testValidRequestReturns201(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects(self::once())->method('persist');
        $em->expects(self::once())->method('flush');

        self::assertSame(201, $this->makeController(em: $em)->create(
            $this->makeRequest(json_encode(['lat' => 14.65, 'lng' => -61.0, 'hasSeaweed' => true]))
        )->getStatusCode());
    }

    public function testValidDensityIsAccepted(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects(self::once())->method('persist');
        $em->expects(self::once())->method('flush');

        self::assertSame(201, $this->makeController(em: $em)->create(
            $this->makeRequest(json_encode(['lat' => 14.65, 'lng' => -61.0, 'hasSeaweed' => true, 'density' => 'high']))
        )->getStatusCode());
    }

    public function testInvalidDensityIsIgnoredAndRequestSucceeds(): void
    {
        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects(self::once())->method('persist');
        $em->expects(self::once())->method('flush');

        self::assertSame(201, $this->makeController(em: $em)->create(
            $this->makeRequest(json_encode(['lat' => 14.65, 'lng' => -61.0, 'hasSeaweed' => true, 'density' => 'extreme']))
        )->getStatusCode());
    }

    // ── Rate limiting ─────────────────────────────────────────────────────────

    public function testRateLimitExceededReturns429(): void
    {
        self::assertSame(429, $this->makeController(exhaust: true)->create(
            $this->makeRequest(json_encode(['lat' => 14.65, 'lng' => -61.0, 'hasSeaweed' => true]))
        )->getStatusCode());
    }
}
