<?php

namespace App\Tests\Shared;

use App\Config\Repository\SiteConfigRepository;
use App\Config\Service\MaintenanceService;
use App\Tests\ApiTestCase;
use Doctrine\ORM\EntityManagerInterface;

/**
 * Maintenance mode is enforced centrally, so these cases cover the three ways a
 * request can legitimately get through (whitelist, allowed IP, admin) plus the
 * blocking behaviour for everyone else.
 */
class MaintenanceModeTest extends ApiTestCase
{
    private SiteConfigRepository $configRepo;
    private MaintenanceService   $maintenance;

    protected function setUp(): void
    {
        parent::setUp();
        $this->configRepo  = static::getContainer()->get(SiteConfigRepository::class);
        $this->maintenance = static::getContainer()->get(MaintenanceService::class);
    }

    protected function tearDown(): void
    {
        $this->setMaintenance('0');
        parent::tearDown();
    }

    private function setMaintenance(string $enabled, string $allowedIps = ''): void
    {
        $this->configRepo->set('maintenance_enabled', $enabled);
        $this->configRepo->set('maintenance_allowed_ips', $allowedIps);
        static::getContainer()->get(EntityManagerInterface::class)->flush();
        $this->maintenance->invalidate();
    }

    public function testAnonymousApiRequestIsBlockedWithMaintenanceEnvelope(): void
    {
        $this->setMaintenance('1');

        $this->client->request('GET', '/api/v1/doctors');
        $response = $this->client->getResponse();
        $body     = json_decode($response->getContent(), true);

        self::assertSame(503, $response->getStatusCode());
        self::assertSame('MAINTENANCE_MODE', $body['errors'][0]['code']);
        self::assertFalse($body['success']);
        self::assertNull($body['data']);
        self::assertNotEmpty($response->headers->get('Retry-After'));
    }

    public function testRoutingErrorsAlsoReturnMaintenance(): void
    {
        $this->setMaintenance('1');

        // 404 is thrown by the router at a higher priority than the request
        // listener, so it is only covered by the kernel.exception path.
        $this->client->request('GET', '/api/v1/definitely-not-a-route');

        self::assertSame(503, $this->responseCode());
        self::assertSame(
            'MAINTENANCE_MODE',
            json_decode($this->client->getResponse()->getContent(), true)['errors'][0]['code'],
        );
    }

    public function testAdminBypassesMaintenance(): void
    {
        $this->setMaintenance('1');
        $admin = $this->createUser(['ROLE_USER', 'ROLE_ADMIN']);

        $this->authJson('GET', '/api/v1/admin/pre-registrations', $admin);

        self::assertNotSame(503, $this->responseCode());
    }

    public function testNonAdminRoleIsBlocked(): void
    {
        $this->setMaintenance('1');
        $doctor = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);

        $body = $this->authJson('GET', '/api/v1/admin/pre-registrations', $doctor);

        self::assertSame(503, $this->responseCode());
        self::assertSame('MAINTENANCE_MODE', $body['errors'][0]['code']);
    }

    public function testSettingsEndpointStaysReachableSoMaintenanceCanBeTurnedOff(): void
    {
        $this->setMaintenance('1');
        $admin = $this->createUser(['ROLE_USER', 'ROLE_ADMIN']);

        $this->authJson('GET', '/api/v1/admin/settings', $admin);

        self::assertSame(200, $this->responseCode());
    }

    public function testAllowedIpBypassesWithoutAuthentication(): void
    {
        $this->setMaintenance('1', '127.0.0.1, 10.0.0.1');

        $this->client->request('GET', '/api/v1/doctors', server: ['REMOTE_ADDR' => '10.0.0.1']);

        self::assertNotSame(503, $this->responseCode());
    }

    public function testDisabledMaintenanceLetsEveryoneThrough(): void
    {
        $this->setMaintenance('0');

        $this->client->request('GET', '/api/v1/doctors');

        self::assertNotSame(503, $this->responseCode());
    }
}
