<?php

declare(strict_types=1);

namespace App\Application\Jobs;

use App\Domain\Enrichment\EnrichmentService;
use App\Domain\Ip\IpAddress;
use App\Domain\Jobs\Job;
use App\Domain\Jobs\JobContext;
use App\Domain\Jobs\JobResult;
use App\Infrastructure\Enrichment\MmdbEnrichmentService;
use App\Infrastructure\Reputation\IpEnrichmentRepository;

/**
 * Pulls a batch of IPs missing enrichment, looks each one up against the
 * configured MMDBs, and upserts the result. Skips empty results so a
 * missing-DB scenario doesn't poison `ip_enrichment` with all-null rows
 * (the next run, after refresh-geoip populates the files, finds them
 * still pending and fills them in correctly).
 *
 * Idempotent by construction: `findPending` already excludes rows whose
 * `enriched_at` is non-null.
 */
final class EnrichPendingJob implements Job
{
    public const NAME = 'enrich-pending';

    private const BATCH_LIMIT = 200;

    public function __construct(
        private readonly EnrichmentService $enrichment,
        private readonly IpEnrichmentRepository $repo,
    ) {
    }

    public function name(): string
    {
        return self::NAME;
    }

    public function defaultIntervalSeconds(): int
    {
        return 300;
    }

    public function maxRuntimeSeconds(): int
    {
        return 60;
    }

    public function run(JobContext $context): JobResult
    {
        // Fast-path: if neither DB is loadable, skip the batch entirely
        // rather than running 200 lookups that all return empty. The
        // generic EnrichmentService interface doesn't expose this; the
        // only concrete impl does.
        if ($this->enrichment instanceof MmdbEnrichmentService && !$this->enrichment->isReady()) {
            $context->logger->warning('enrich_pending_no_db', [
                'note' => 'GeoIP DBs not present — skipping batch',
            ]);

            return new JobResult(itemsProcessed: 0, details: ['db_missing' => true]);
        }

        $pending = $this->repo->findPending(self::BATCH_LIMIT);
        if ($pending === []) {
            return new JobResult(itemsProcessed: 0);
        }

        $processed = 0;
        $skipped = 0;
        foreach ($pending as $ipBin) {
            $ip = IpAddress::fromBinary($ipBin);
            $result = $this->enrichment->enrich($ip);
            if ($result->isEmpty()) {
                $skipped++;

                continue;
            }
            $this->repo->upsert($ipBin, $result);
            $processed++;
        }

        return new JobResult(
            itemsProcessed: $processed,
            details: ['skipped_empty' => $skipped, 'batch_size' => count($pending)],
        );
    }
}
