<?php

namespace Tests\Feature\Services;

use App\Services\ApiUsageTracker;
use Illuminate\Support\Facades\Redis;
use Mtc\MercuryDataModels\ApiUsage;
use Mtc\MercuryDataModels\ApiUsageEndpoint;
use Tests\DatabaseTestCase;

class ApiUsageTrackerTest extends DatabaseTestCase
{
    private ApiUsageTracker $tracker;
    private string $prefix;

    protected function setUp(): void
    {
        parent::setUp();
        // Isolate this test's Redis keys so parallel tests don't drain each other.
        $this->prefix = 'api-usage-test-' . uniqid();
        config(['api-usage.redis_prefix' => $this->prefix]);

        // The endpoint id cache is process-static; each test runs against a freshly
        // recreated database, so clear it to avoid pointing at stale ids.
        ApiUsageEndpoint::clearIdCache();

        // These are Redis integration tests; skip where Redis (or the phpredis
        // extension) isn't available, e.g. CI runners without a Redis service.
        try {
            Redis::connection()->ping();
        } catch (\Throwable $exception) {
            $this->markTestSkipped('Redis is not available: ' . $exception->getMessage());
        }

        $this->tracker = new ApiUsageTracker();
    }

    protected function tearDown(): void
    {
        try {
            Redis::del($this->prefix . ':buckets');
        } catch (\Throwable) {
        }
        parent::tearDown();
    }

    public function testAggregatesRequestsAndFlushesOneRowToDatabase(): void
    {
        foreach ([0.10, 0.30, 0.20] as $length) {
            $this->tracker->record('tenant-a', '/api/x', null, 'GET', 200, $length);
        }

        $this->tracker->flush();

        $usage = ApiUsage::query()
            ->where('tenant_id', 'tenant-a')
            ->whereHas('endpointEntry', fn ($query) => $query->where('endpoint', '/api/x'))
            ->where('method', 'GET')
            ->first();

        $this->assertNotNull($usage);
        // New rows normalize the endpoint to endpoint_id and leave the varchar null.
        $this->assertNull($usage->endpoint);
        $this->assertNotNull($usage->endpoint_id);
        $this->assertSame(3, (int) $usage->hits);
        $this->assertEqualsWithDelta(0.20, (float) $usage->avg, 0.0001);
        $this->assertEqualsWithDelta(0.10, (float) $usage->min, 0.0001);
        $this->assertEqualsWithDelta(0.30, (float) $usage->max, 0.0001);
    }

    public function testMergesAcrossFlushWindowsIntoSameRow(): void
    {
        $this->tracker->record('tenant-a', '/api/y', null, 'GET', 200, 0.40);
        $this->tracker->flush();

        $this->tracker->record('tenant-a', '/api/y', null, 'GET', 200, 0.20);
        $this->tracker->flush();

        $usage = ApiUsage::query()
            ->where('tenant_id', 'tenant-a')
            ->whereHas('endpointEntry', fn ($query) => $query->where('endpoint', '/api/y'))
            ->first();

        $this->assertSame(2, (int) $usage->hits);
        $this->assertEqualsWithDelta(0.30, (float) $usage->avg, 0.0001);
        $this->assertEqualsWithDelta(0.20, (float) $usage->min, 0.0001);
        $this->assertEqualsWithDelta(0.40, (float) $usage->max, 0.0001);

        // The endpoint string is stored once and reused, not duplicated per flush.
        $this->assertSame(1, ApiUsageEndpoint::query()->where('endpoint', '/api/y')->count());
    }

    public function testFlushWithNothingBufferedWritesNothing(): void
    {
        $this->tracker->flush();

        $this->assertSame(0, ApiUsage::query()->where('tenant_id', 'tenant-a')->count());
    }

    public function testCapturesSnapshotForSlowPostRequestAgainstNormalizedRow(): void
    {
        // A slow POST with input becomes a snapshot; matching happens by endpoint_id.
        $this->tracker->record('tenant-a', '/api/slow', ['foo' => 'bar'], 'POST', 200, 5.0);
        $this->tracker->flush();

        $usage = ApiUsage::query()
            ->whereHas('endpointEntry', fn ($query) => $query->where('endpoint', '/api/slow'))
            ->where('method', 'POST')
            ->first();

        $this->assertNotNull($usage);
        $this->assertSame(1, $usage->snapshots()->count());
        $this->assertEqualsWithDelta(5.0, (float) $usage->snapshots()->first()->request_time, 0.0001);
    }
}
