<?php

declare(strict_types=1);

namespace App\Tests\Functional;

use App\DataFixtures\SignalFixtures;
use App\Entity\Signal;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Response;

class SignalControllerTest extends WebTestCase
{
    private KernelBrowser $client;

    private EntityManagerInterface $entityManager;

    protected function setUp(): void
    {
        $this->client = static::createClient();
        $entityManager = $this->client->getContainer()->get(EntityManagerInterface::class);
        self::assertInstanceOf(EntityManagerInterface::class, $entityManager);
        $this->entityManager = $entityManager;

        $metadata = $this->entityManager->getMetadataFactory()->getAllMetadata();
        $schemaTool = new SchemaTool($this->entityManager);
        $schemaTool->dropDatabase();
        if ($metadata !== []) {
            $schemaTool->createSchema($metadata);
        }

        new SignalFixtures()->load($this->entityManager);
    }

    #[\Override]
    protected function tearDown(): void
    {
        parent::tearDown();

        if (isset($this->entityManager)) {
            $this->entityManager->close();
            unset($this->entityManager);
        }
    }

    public function testIndexReturnsRecentSignals(): void
    {
        $this->client->request('GET', '/api/signals', server: [
            'HTTP_ACCEPT' => 'application/json',
        ]);

        self::assertResponseIsSuccessful();

        $content = $this->client->getResponse()->getContent();
        self::assertIsString($content);
        $payload = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        self::assertSame(substr(hash('sha256', '127.0.0.1'), 0, 12), $payload['clientKey']);
        self::assertCount(3, $payload['points']);
        self::assertSame(3, $payload['totals']['points']);
        self::assertSame(2, $payload['totals']['contributors']);
        self::assertSame(-11.6852, $payload['points'][0]['signalLocation']['lat']);
        self::assertArrayNotHasKey('userLocation', $payload['points'][0]);
    }

    public function testIndexRespectsLimitQueryParameter(): void
    {
        $this->client->request('GET', '/api/signals?limit=1', server: [
            'HTTP_ACCEPT' => 'application/json',
        ]);

        self::assertResponseIsSuccessful();

        $content = $this->client->getResponse()->getContent();
        self::assertIsString($content);
        $payload = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        self::assertCount(1, $payload['points']);
        self::assertSame(1, $payload['totals']['points']);
    }

    public function testStorePersistsNewSignal(): void
    {
        $body = [
            'signalLocation' => [
                'lat' => -11.6901,
                'lng' => 27.4959,
            ],
            'userLocation' => [
                'lat' => -11.6899,
                'lng' => 27.4962,
            ],
        ];

        $this->client->request('POST', '/api/signals', server: [
            'CONTENT_TYPE' => 'application/json',
            'HTTP_ACCEPT' => 'application/json',
        ], content: json_encode($body, JSON_THROW_ON_ERROR));

        self::assertResponseStatusCodeSame(Response::HTTP_CREATED);

        $content = $this->client->getResponse()->getContent();
        self::assertIsString($content);
        $payload = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
        self::assertSame('stored', $payload['status']);
        self::assertSame($body['signalLocation']['lat'], $payload['point']['signalLocation']['lat']);
        self::assertSame($body['signalLocation']['lng'], $payload['point']['signalLocation']['lng']);
        self::assertArrayNotHasKey('userLocation', $payload['point']);

        $repository = $this->entityManager->getRepository(Signal::class);
        $signals = $repository->findAll();
        self::assertCount(4, $signals);
    }

    public function testStoreRejectsInvalidCoordinates(): void
    {
        $body = [
            'signalLocation' => [
                'lat' => 181,
                'lng' => 10,
            ],
            'userLocation' => [
                'lat' => 10,
                'lng' => 10,
            ],
        ];

        $this->client->request('POST', '/api/signals', server: [
            'CONTENT_TYPE' => 'application/json',
            'HTTP_ACCEPT' => 'application/json',
        ], content: json_encode($body, JSON_THROW_ON_ERROR));

        self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);

        $response = $this->client->getResponse();
        self::assertSame('invalid_coordinates', $response->headers->get('x-error-code'));

        $content = $response->getContent();
        self::assertIsString($content);
        $payload = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
        self::assertSame('Latitude 181.000000 is out of bounds.', $payload['detail'] ?? null);
    }

    public function testStoreRejectsPointTooFar(): void
    {
        $body = [
            'signalLocation' => [
                'lat' => -11.6901,
                'lng' => 27.4959,
            ],
            'userLocation' => [
                'lat' => -11.7005,
                'lng' => 27.4804,
            ],
        ];

        $this->client->request('POST', '/api/signals', server: [
            'CONTENT_TYPE' => 'application/json',
            'HTTP_ACCEPT' => 'application/json',
        ], content: json_encode($body, JSON_THROW_ON_ERROR));

        self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);

        $response = $this->client->getResponse();
        self::assertSame('point_too_far', $response->headers->get('x-error-code'));

        $content = $response->getContent();
        self::assertIsString($content);
        $payload = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
        self::assertSame('The submitted point must be within 1.00km of your location.', $payload['detail'] ?? null);
    }
}
