<?php

declare(strict_types=1);

namespace App\Tests\Unit\Enrichment;

use App\Domain\Ip\IpAddress;
use App\Domain\Time\Clock;
use App\Domain\Time\SystemClock;
use App\Infrastructure\Enrichment\MaxMindRecordAdapter;
use App\Infrastructure\Enrichment\MmdbEnrichmentService;
use Monolog\Handler\TestHandler;
use Monolog\Logger;
use PHPUnit\Framework\TestCase;

/**
 * Drives MmdbEnrichmentService against the vendored MaxMind test
 * fixtures (Apache-2.0). Covers the load → lookup → result → reload
 * lifecycle plus the warn-once-on-missing-DB invariant.
 */
final class MmdbEnrichmentServiceTest extends TestCase
{
    private const COUNTRY_DB = __DIR__ . '/../../Fixtures/geoip/country.mmdb';
    private const ASN_DB = __DIR__ . '/../../Fixtures/geoip/asn.mmdb';

    public function testLooksUpKnownIpv4Fixture(): void
    {
        $service = $this->makeService(self::COUNTRY_DB, self::ASN_DB);
        $result = $service->enrich(IpAddress::fromString('81.2.69.142'));
        self::assertSame('GB', $result->countryCode);
        // The fixture's ASN db doesn't carry every test IP — country alone is the load-bearing assertion.
    }

    public function testIpv6Lookup(): void
    {
        $service = $this->makeService(self::COUNTRY_DB, self::ASN_DB);
        // The MaxMind fixture covers v4-mapped v6 of 81.2.69.142.
        $result = $service->enrich(IpAddress::fromString('::ffff:81.2.69.142'));
        self::assertSame('GB', $result->countryCode);
    }

    public function testUnknownIpReturnsEmpty(): void
    {
        $service = $this->makeService(self::COUNTRY_DB, self::ASN_DB);
        // A reserved-but-not-in-fixture IP.
        $result = $service->enrich(IpAddress::fromString('192.0.2.99'));
        self::assertNull($result->countryCode);
        self::assertNull($result->asn);
        self::assertTrue($result->isEmpty());
    }

    public function testMissingDbWarnsOnceAndReturnsEmpty(): void
    {
        $logger = new Logger('t');
        $handler = new TestHandler();
        $logger->pushHandler($handler);

        $service = new MmdbEnrichmentService(
            countryDbPath: '/nonexistent/country.mmdb',
            asnDbPath: '/nonexistent/asn.mmdb',
            adapter: new MaxMindRecordAdapter(),
            clock: new SystemClock(),
            logger: $logger,
        );

        // Two lookups should still produce only one warning.
        $a = $service->enrich(IpAddress::fromString('1.2.3.4'));
        $b = $service->enrich(IpAddress::fromString('5.6.7.8'));
        self::assertTrue($a->isEmpty());
        self::assertTrue($b->isEmpty());
        self::assertFalse($service->isReady());

        $messages = array_map(static fn ($r): string => $r->message, $handler->getRecords());
        $matches = array_filter($messages, static fn (string $m): bool => $m === 'geoip_db_unavailable');
        self::assertCount(1, $matches);
    }

    public function testReloadReadersClearsState(): void
    {
        $service = $this->makeService(self::COUNTRY_DB, self::ASN_DB);
        $service->enrich(IpAddress::fromString('81.2.69.142'));
        $service->reloadReaders();
        // Should still work after reload.
        $result = $service->enrich(IpAddress::fromString('81.2.69.142'));
        self::assertSame('GB', $result->countryCode);
    }

    private function makeService(string $country, string $asn, ?Clock $clock = null): MmdbEnrichmentService
    {
        $logger = new Logger('t');
        $logger->pushHandler(new TestHandler());

        return new MmdbEnrichmentService(
            countryDbPath: $country,
            asnDbPath: $asn,
            adapter: new MaxMindRecordAdapter(),
            clock: $clock ?? new SystemClock(),
            logger: $logger,
        );
    }
}
