<?php

namespace Tests\Tenant;

use App\Jobs\HandleAutoTraderHubNotification;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Support\Facades\Cache;
use Mtc\MercuryDataModels\ApiNotification;
use Tests\TenantTestCase;

class HandleAutoTraderHubNotificationTest extends TenantTestCase
{
    /**
     * AUTO-1640: AutoTrader fires two STOCK_UPDATE webhooks for the same vehicle in the
     * same second. Two queue workers race the check-then-act in exists()/add() and create
     * duplicate rows. WithoutOverlapping keyed on stock_id serialises execution per
     * vehicle, so the second job sees the row committed by the first.
     */
    public function testMiddlewareSerialisesPerStockId(): void
    {
        $job = new HandleAutoTraderHubNotification(
            ['type' => 'STOCK_UPDATE', 'stock_id' => 'abc-123', 'data' => []],
            new ApiNotification(),
        );

        $middleware = $job->middleware();

        $this->assertCount(1, $middleware);
        $this->assertInstanceOf(WithoutOverlapping::class, $middleware[0]);
        $this->assertSame('at-hub-abc-123', $middleware[0]->key);
        $this->assertSame(5, $middleware[0]->releaseAfter);
        $this->assertSame(60, $middleware[0]->expiresAfter);
    }

    public function testMiddlewareFallsBackToGlobalKeyWhenStockIdMissing(): void
    {
        $job = new HandleAutoTraderHubNotification(
            ['type' => 'STOCK_UPDATE', 'data' => []],
            new ApiNotification(),
        );

        $middleware = $job->middleware();

        $this->assertSame('at-hub-no-stock-id', $middleware[0]->key);
    }

    /**
     * AUTO-1640: simulates a realistic burst of incoming webhooks where some vehicles
     * receive duplicate notifications and others do not. Proves the lock blocks duplicates
     * for the same stockId (so the race that creates duplicate vehicle rows is closed)
     * while leaving unrelated vehicles free to process in parallel.
     */
    public function testLockBlocksDuplicatesPerStockIdButAllowsDifferentVehiclesInParallel(): void
    {
        $heldLocks = [];

        // First webhook arrives for each of four vehicles - each acquires its own lock.
        foreach (['STOCK-A', 'STOCK-B', 'STOCK-C', 'STOCK-D'] as $stockId) {
            $job = new HandleAutoTraderHubNotification(
                ['type' => 'STOCK_UPDATE', 'stock_id' => $stockId, 'data' => []],
                new ApiNotification(),
            );
            $middleware = $job->middleware()[0];

            $lock = Cache::lock($middleware->getLockKey($job), 60);

            $this->assertTrue(
                $lock->get(),
                "First webhook for {$stockId} should acquire its lock"
            );
            $heldLocks[] = $lock;
        }

        try {
            // Duplicate webhooks arrive for STOCK-A and STOCK-C while their first webhooks
            // are still in flight. Both should be blocked (would be released back to queue
            // by the middleware and retried after releaseAfter seconds).
            foreach (['STOCK-A', 'STOCK-C'] as $stockId) {
                $duplicateJob = new HandleAutoTraderHubNotification(
                    ['type' => 'STOCK_UPDATE', 'stock_id' => $stockId, 'data' => []],
                    new ApiNotification(),
                );
                $duplicateMiddleware = $duplicateJob->middleware()[0];

                $duplicateLock = Cache::lock(
                    $duplicateMiddleware->getLockKey($duplicateJob),
                    60,
                );

                $this->assertFalse(
                    $duplicateLock->get(),
                    "Duplicate webhook for {$stockId} should be blocked while the first is in flight"
                );
            }

            // A genuinely new vehicle (STOCK-E) arriving at the same time is NOT blocked.
            // Different stockIds get independent locks, so parallelism across vehicles is
            // preserved - only same-vehicle webhooks serialise.
            $newJob = new HandleAutoTraderHubNotification(
                ['type' => 'STOCK_UPDATE', 'stock_id' => 'STOCK-E', 'data' => []],
                new ApiNotification(),
            );
            $newMiddleware = $newJob->middleware()[0];

            $newLock = Cache::lock($newMiddleware->getLockKey($newJob), 60);

            $this->assertTrue(
                $newLock->get(),
                'A different stockId should acquire its own independent lock and run in parallel'
            );
            $newLock->release();
        } finally {
            foreach ($heldLocks as $lock) {
                $lock->release();
            }
        }
    }
}
