<?php

declare(strict_types=1);

namespace App\Tests\Integration\Enrichment;

use App\Application\Jobs\EnrichPendingJob;
use App\Domain\Ip\IpAddress;
use App\Domain\Jobs\JobContext;
use App\Domain\Time\SystemClock;
use App\Infrastructure\Enrichment\MaxMindRecordAdapter;
use App\Infrastructure\Enrichment\MmdbEnrichmentService;
use App\Infrastructure\Reputation\IpEnrichmentRepository;
use App\Tests\Integration\Support\AppTestCase;
use Monolog\Handler\TestHandler;
use Monolog\Logger;

/**
 * Drives the full enrich-pending flow: seed reports -> run job ->
 * assert that the touched IPs land in `ip_enrichment`.
 */
final class EnrichPendingJobTest extends AppTestCase
{
    private const COUNTRY_DB = __DIR__ . '/../../Fixtures/geoip/country.mmdb';
    private const ASN_DB = __DIR__ . '/../../Fixtures/geoip/asn.mmdb';

    public function testEnrichesPendingIps(): void
    {
        // Pre-condition: a single reporter and report row for 81.2.69.142 (in the fixture as GB).
        $reporterId = $this->createReporter('test-rep');
        $categoryId = (int) $this->db->fetchOne("SELECT id FROM categories WHERE slug = 'brute_force'");
        $ip = IpAddress::fromString('81.2.69.142');
        $this->db->insert('reports', [
            'ip_bin' => $ip->binary(),
            'ip_text' => $ip->text(),
            'category_id' => $categoryId,
            'reporter_id' => $reporterId,
            'weight_at_report' => '1.0000',
            'received_at' => (new \DateTimeImmutable('-1 minute', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s'),
            'metadata_json' => null,
        ], ['ip_bin' => \Doctrine\DBAL\ParameterType::LARGE_OBJECT]);

        // Replace the container's EnrichmentService with one pointed at the fixtures.
        $service = new MmdbEnrichmentService(
            countryDbPath: self::COUNTRY_DB,
            asnDbPath: self::ASN_DB,
            adapter: new MaxMindRecordAdapter(),
            clock: new SystemClock(),
            logger: new Logger('t', [new TestHandler()]),
        );

        /** @var IpEnrichmentRepository $repo */
        $repo = $this->container->get(IpEnrichmentRepository::class);

        $job = new EnrichPendingJob($service, $repo);
        $context = new JobContext(new SystemClock(), new Logger('t', [new TestHandler()]), []);
        $result = $job->run($context);

        self::assertSame(1, $result->itemsProcessed);

        $row = $repo->findByIpBin($ip->binary());
        self::assertNotNull($row);
        self::assertSame('GB', $row['country_code']);
    }

    public function testNoOpWhenDbsAreMissing(): void
    {
        // Use bogus paths.
        $service = new MmdbEnrichmentService(
            countryDbPath: '/no/where/country.mmdb',
            asnDbPath: '/no/where/asn.mmdb',
            adapter: new MaxMindRecordAdapter(),
            clock: new SystemClock(),
            logger: new Logger('t', [new TestHandler()]),
        );

        // Seed something so findPending would otherwise return rows.
        $reporterId = $this->createReporter('test-rep');
        $categoryId = (int) $this->db->fetchOne("SELECT id FROM categories WHERE slug = 'brute_force'");
        $ip = IpAddress::fromString('203.0.113.7');
        $this->db->insert('reports', [
            'ip_bin' => $ip->binary(),
            'ip_text' => $ip->text(),
            'category_id' => $categoryId,
            'reporter_id' => $reporterId,
            'weight_at_report' => '1.0000',
            'received_at' => (new \DateTimeImmutable('-1 minute', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s'),
            'metadata_json' => null,
        ], ['ip_bin' => \Doctrine\DBAL\ParameterType::LARGE_OBJECT]);

        /** @var IpEnrichmentRepository $repo */
        $repo = $this->container->get(IpEnrichmentRepository::class);
        $job = new EnrichPendingJob($service, $repo);
        $result = $job->run(new JobContext(new SystemClock(), new Logger('t', [new TestHandler()]), []));

        self::assertSame(0, $result->itemsProcessed);
        self::assertNull($repo->findByIpBin($ip->binary()));
    }

    public function testFindPendingExcludesAlreadyEnriched(): void
    {
        $service = new MmdbEnrichmentService(
            countryDbPath: self::COUNTRY_DB,
            asnDbPath: self::ASN_DB,
            adapter: new MaxMindRecordAdapter(),
            clock: new SystemClock(),
            logger: new Logger('t', [new TestHandler()]),
        );

        $reporterId = $this->createReporter('test-rep');
        $categoryId = (int) $this->db->fetchOne("SELECT id FROM categories WHERE slug = 'brute_force'");
        $ip = IpAddress::fromString('81.2.69.142');
        $this->db->insert('reports', [
            'ip_bin' => $ip->binary(),
            'ip_text' => $ip->text(),
            'category_id' => $categoryId,
            'reporter_id' => $reporterId,
            'weight_at_report' => '1.0000',
            'received_at' => (new \DateTimeImmutable('-1 minute', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s'),
            'metadata_json' => null,
        ], ['ip_bin' => \Doctrine\DBAL\ParameterType::LARGE_OBJECT]);

        /** @var IpEnrichmentRepository $repo */
        $repo = $this->container->get(IpEnrichmentRepository::class);

        $job = new EnrichPendingJob($service, $repo);
        $first = $job->run(new JobContext(new SystemClock(), new Logger('t', [new TestHandler()]), []));
        self::assertSame(1, $first->itemsProcessed);

        // Second run: nothing left to do.
        $second = $job->run(new JobContext(new SystemClock(), new Logger('t', [new TestHandler()]), []));
        self::assertSame(0, $second->itemsProcessed);
    }

    public function testReenrichClearAllowsReprocessing(): void
    {
        $service = new MmdbEnrichmentService(
            countryDbPath: self::COUNTRY_DB,
            asnDbPath: self::ASN_DB,
            adapter: new MaxMindRecordAdapter(),
            clock: new SystemClock(),
            logger: new Logger('t', [new TestHandler()]),
        );
        $reporterId = $this->createReporter('test-rep');
        $categoryId = (int) $this->db->fetchOne("SELECT id FROM categories WHERE slug = 'brute_force'");
        $ip = IpAddress::fromString('81.2.69.142');
        $this->db->insert('reports', [
            'ip_bin' => $ip->binary(),
            'ip_text' => $ip->text(),
            'category_id' => $categoryId,
            'reporter_id' => $reporterId,
            'weight_at_report' => '1.0000',
            'received_at' => (new \DateTimeImmutable('-1 minute', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s'),
            'metadata_json' => null,
        ], ['ip_bin' => \Doctrine\DBAL\ParameterType::LARGE_OBJECT]);

        /** @var IpEnrichmentRepository $repo */
        $repo = $this->container->get(IpEnrichmentRepository::class);
        $job = new EnrichPendingJob($service, $repo);
        $job->run(new JobContext(new SystemClock(), new Logger('t', [new TestHandler()]), []));

        $cleared = $repo->clearAllEnrichedAt();
        self::assertSame(1, $cleared);

        $reRun = $job->run(new JobContext(new SystemClock(), new Logger('t', [new TestHandler()]), []));
        self::assertSame(1, $reRun->itemsProcessed);
    }
}
