<?php

declare(strict_types=1);

namespace App\Tests\Unit\Reputation;

use App\Domain\Ip\Cidr;
use App\Domain\Ip\IpAddress;
use App\Domain\Reputation\CidrEvaluator;
use PHPUnit\Framework\TestCase;

/**
 * Unit-level coverage of the in-memory containment math: v4, v6, IPv4
 * mapped into v6, and the empty-evaluator no-op shape.
 */
final class CidrEvaluatorTest extends TestCase
{
    public function testEmptyEvaluatorMatchesNothing(): void
    {
        $evaluator = new CidrEvaluator([], [], [], []);
        $ip = IpAddress::fromString('203.0.113.42');

        self::assertFalse($evaluator->isAllowlisted($ip));
        self::assertFalse($evaluator->isManuallyBlocked($ip));
        self::assertSame([], $evaluator->manualBlockedSubnets());
        self::assertSame([], $evaluator->allowlistedSubnets());
    }

    public function testSingleIpManualBlockMatches(): void
    {
        $bin = IpAddress::fromString('203.0.113.42')->binary();
        $evaluator = new CidrEvaluator([$bin], [], [], []);

        self::assertTrue($evaluator->isManuallyBlocked(IpAddress::fromString('203.0.113.42')));
        self::assertFalse($evaluator->isManuallyBlocked(IpAddress::fromString('203.0.113.43')));
    }

    public function testV4SubnetContainsHostV4(): void
    {
        $cidr = Cidr::fromString('203.0.113.0/24');
        $evaluator = new CidrEvaluator([], [$cidr], [], []);

        self::assertTrue($evaluator->isManuallyBlocked(IpAddress::fromString('203.0.113.42')));
        self::assertFalse($evaluator->isManuallyBlocked(IpAddress::fromString('203.0.114.42')));
    }

    public function testV6SubnetContainsV6Host(): void
    {
        $cidr = Cidr::fromString('2001:db8::/32');
        $evaluator = new CidrEvaluator([], [$cidr], [], []);

        self::assertTrue($evaluator->isManuallyBlocked(IpAddress::fromString('2001:db8::1')));
        self::assertTrue($evaluator->isManuallyBlocked(IpAddress::fromString('2001:db8:abcd::42')));
        self::assertFalse($evaluator->isManuallyBlocked(IpAddress::fromString('2001:db9::1')));
    }

    public function testIpv4MappedV6RoundTripsAsV4(): void
    {
        // Per IpAddress: ::ffff:203.0.113.42 normalizes to the v4-mapped binary
        // of 203.0.113.42 — i.e. it should match a v4 /24 manual block.
        $cidr = Cidr::fromString('203.0.113.0/24');
        $evaluator = new CidrEvaluator([], [$cidr], [], []);

        $ip = IpAddress::fromString('::ffff:203.0.113.42');
        self::assertTrue($evaluator->isManuallyBlocked($ip));
    }

    public function testAllowlistAndBlockOverlapAreIndependent(): void
    {
        // Both lists "see" the same IP — the evaluator just reports each
        // independently. SPEC §5 precedence is the responsibility of the
        // caller (EffectiveStatusService).
        $bin = IpAddress::fromString('198.51.100.5')->binary();
        $evaluator = new CidrEvaluator([$bin], [], [$bin], []);

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

    public function testManualBlockedSubnetsExposesSingleEntryForLargeCidr(): void
    {
        $cidr = Cidr::fromString('10.0.0.0/16');
        $evaluator = new CidrEvaluator([], [$cidr], [], []);

        self::assertCount(1, $evaluator->manualBlockedSubnets());
        self::assertSame('10.0.0.0/16', $evaluator->manualBlockedSubnets()[0]->text());
    }

    public function testAllowlistedSubnetsExposesEntries(): void
    {
        $a = Cidr::fromString('10.0.0.0/8');
        $b = Cidr::fromString('192.168.0.0/16');
        $evaluator = new CidrEvaluator([], [], [], [$a, $b]);

        self::assertCount(2, $evaluator->allowlistedSubnets());
    }
}
