<?php

declare(strict_types=1);

namespace App\Tests\Integration\Admin;

use App\Domain\Auth\Role;
use App\Domain\Auth\TokenKind;
use App\Domain\Ip\IpAddress;
use App\Infrastructure\Reputation\CidrEvaluatorFactory;
use App\Tests\Integration\Support\AppTestCase;
use Monolog\Handler\TestHandler;
use Monolog\Level;
use Monolog\Logger;
use Psr\Log\LoggerInterface;

/**
 * Covers SPEC §6 allowlist CRUD plus the allowlist-precedence warning
 * logged when an IP appears on both lists. Mirrors the manual-blocks
 * suite minus `expires_at`.
 */
final class AllowlistControllerTest extends AppTestCase
{
    public function testOperatorCanCreateIpAllowlistEntry(): void
    {
        $token = $this->createToken(TokenKind::Admin, role: Role::Operator);
        $response = $this->request(
            'POST',
            '/api/v1/admin/allowlist',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'ip', 'ip' => '198.51.100.5', 'reason' => 'monitor']) ?: null,
        );
        self::assertSame(201, $response->getStatusCode());
        $body = $this->decode($response);
        self::assertSame('198.51.100.5', $body['ip']);
    }

    public function testOperatorCanCreateSubnetAllowlistEntry(): void
    {
        $token = $this->createToken(TokenKind::Admin, role: Role::Operator);
        $response = $this->request(
            'POST',
            '/api/v1/admin/allowlist',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'subnet', 'cidr' => '10.0.0.0/8', 'reason' => 'private']) ?: null,
        );
        self::assertSame(201, $response->getStatusCode());
        $body = $this->decode($response);
        self::assertSame('10.0.0.0/8', $body['cidr']);
        self::assertSame(8, $body['prefix_length']);
    }

    public function testViewerCannotCreate(): void
    {
        $token = $this->createToken(TokenKind::Admin, role: Role::Viewer);
        $response = $this->request(
            'POST',
            '/api/v1/admin/allowlist',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'ip', 'ip' => '1.2.3.4']) ?: null,
        );
        self::assertSame(403, $response->getStatusCode());
    }

    public function testAllowlistedIpIsAllowlistedRegardlessOfManualBlock(): void
    {
        $token = $this->createToken(TokenKind::Admin, role: Role::Admin);

        // Same IP on both lists — log should warn; evaluator reports both
        // memberships independently; SPEC precedence is the
        // EffectiveStatusService's job.
        $this->request(
            'POST',
            '/api/v1/admin/manual-blocks',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'ip', 'ip' => '198.51.100.5']) ?: null,
        );
        $this->request(
            'POST',
            '/api/v1/admin/allowlist',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'ip', 'ip' => '198.51.100.5']) ?: null,
        );

        // Inject a TestHandler-backed logger before the evaluator builds.
        $logger = new Logger('test');
        $logger->pushHandler($handler = new TestHandler(Level::Warning));
        /** @var \DI\Container $container */
        $container = $this->container;
        $container->set(LoggerInterface::class, $logger);
        // Rebuild the factory with the new logger so the warning is captured.
        $container->set(CidrEvaluatorFactory::class, new CidrEvaluatorFactory(
            $container->get(\App\Infrastructure\ManualBlock\ManualBlockRepository::class),
            $container->get(\App\Infrastructure\Allowlist\AllowlistRepository::class),
            $container->get(\App\Domain\Time\Clock::class),
            $logger,
            0,
        ));

        /** @var CidrEvaluatorFactory $factory */
        $factory = $this->container->get(CidrEvaluatorFactory::class);
        $evaluator = $factory->get();

        $ip = IpAddress::fromString('198.51.100.5');
        self::assertTrue($evaluator->isAllowlisted($ip));
        self::assertTrue($evaluator->isManuallyBlocked($ip));

        $found = false;
        foreach ($handler->getRecords() as $record) {
            if (str_contains($record->message, 'allowlist takes precedence')) {
                $found = true;
                break;
            }
        }
        self::assertTrue($found, 'expected overlap warning to be logged');
    }

    public function testDeleteInvalidatesEvaluator(): void
    {
        $token = $this->createToken(TokenKind::Admin, role: Role::Admin);
        $created = $this->request(
            'POST',
            '/api/v1/admin/allowlist',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'subnet', 'cidr' => '10.0.0.0/8']) ?: null,
        );
        $id = (int) $this->decode($created)['id'];

        /** @var CidrEvaluatorFactory $factory */
        $factory = $this->container->get(CidrEvaluatorFactory::class);
        self::assertTrue($factory->get()->isAllowlisted(IpAddress::fromString('10.0.0.1')));

        $delete = $this->request(
            'DELETE',
            "/api/v1/admin/allowlist/{$id}",
            ['Authorization' => 'Bearer ' . $token],
        );
        self::assertSame(204, $delete->getStatusCode());

        self::assertFalse($factory->get()->isAllowlisted(IpAddress::fromString('10.0.0.1')));
    }
}
