<?php

declare(strict_types=1);

use App\Models\ApiKey;
use App\Models\Deal;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Tests\Concerns\SharesSupplierPdo;

uses(DatabaseTransactions::class, SharesSupplierPdo::class);

/** Создаёт активный read-ключ, возвращает plain-ключ. */
function makeApiKey(int $tenantId, int $userId, array $over = []): string
{
    $plain = 'lpkapi_'.Str::random(48);
    ApiKey::create(array_merge([
        'tenant_id' => $tenantId,
        'user_id' => $userId,
        'name' => 'test',
        'key_hash' => Hash::make($plain),
        'key_prefix' => substr($plain, 0, 10),
        'scopes' => ['read'],
        'expires_at' => now()->addYear(),
        'is_active' => true,
        'created_at' => now(),
    ], $over));

    return $plain;
}

test('валидный ключ → 200 и только свои сделки', function () {
    $tenantA = Tenant::factory()->create();
    $userA = User::factory()->create(['tenant_id' => $tenantA->id]);
    $tenantB = Tenant::factory()->create();

    Deal::factory()->count(2)->create(['tenant_id' => $tenantA->id, 'received_at' => now()]);
    Deal::factory()->create(['tenant_id' => $tenantB->id, 'received_at' => now()]);

    $key = makeApiKey($tenantA->id, $userA->id);

    $r = $this->getJson('/api/v1/deals', ['Authorization' => "Bearer {$key}"]);

    $r->assertOk();
    expect($r->json('data'))->toHaveCount(2);
});

test('нет заголовка → 401', function () {
    $this->getJson('/api/v1/deals')->assertStatus(401);
});

test('неверный ключ → 401', function () {
    $tenant = Tenant::factory()->create();
    $user = User::factory()->create(['tenant_id' => $tenant->id]);
    $key = makeApiKey($tenant->id, $user->id);

    // Тот же префикс, но изменённый хвост — Hash::check не пройдёт.
    $bad = substr($key, 0, 10).str_repeat('x', strlen($key) - 10);

    $this->getJson('/api/v1/deals', ['Authorization' => "Bearer {$bad}"])->assertStatus(401);
});

test('просроченный ключ → 401', function () {
    $tenant = Tenant::factory()->create();
    $user = User::factory()->create(['tenant_id' => $tenant->id]);
    $key = makeApiKey($tenant->id, $user->id, ['expires_at' => now()->subDay()]);

    $this->getJson('/api/v1/deals', ['Authorization' => "Bearer {$key}"])->assertStatus(401);
});

test('неактивный ключ → 401', function () {
    $tenant = Tenant::factory()->create();
    $user = User::factory()->create(['tenant_id' => $tenant->id]);
    $key = makeApiKey($tenant->id, $user->id, ['is_active' => false]);

    $this->getJson('/api/v1/deals', ['Authorization' => "Bearer {$key}"])->assertStatus(401);
});

test('last_used_at обновляется после успешного запроса', function () {
    $tenant = Tenant::factory()->create();
    $user = User::factory()->create(['tenant_id' => $tenant->id]);
    $key = makeApiKey($tenant->id, $user->id, ['last_used_at' => null]);

    $this->getJson('/api/v1/deals', ['Authorization' => "Bearer {$key}"])->assertOk();

    $row = ApiKey::where('tenant_id', $tenant->id)->latest('id')->first();
    expect($row->last_used_at)->not->toBeNull();
});

test('route-throttle: 121-й запрос к /api/v1/deals с одного источника → 429', function () {
    // apiv1-rate (приёмка 21.06): публичный read-API без лимита — bcrypt/DB на
    // каждый запрос (brute/DoS-поверхность). Лимит 120/мин на источник (ключ→IP).
    // Бьём безключевыми запросами: throttle стоит ДО apikey, поэтому 1..120 → 401
    // (от apikey, за throttle), 121-й → 429 (от throttle-middleware, до apikey).
    // Это доказывает: лимит навешен на маршрут именно route-throttle'ом.
    for ($i = 1; $i <= 120; $i++) {
        $this->getJson('/api/v1/deals')->assertStatus(401);
    }

    $this->getJson('/api/v1/deals')->assertStatus(429);
});

test('since-фильтр отсекает старые сделки', function () {
    $tenant = Tenant::factory()->create();
    $user = User::factory()->create(['tenant_id' => $tenant->id]);

    Deal::factory()->create(['tenant_id' => $tenant->id, 'received_at' => now()]);
    Deal::factory()->create(['tenant_id' => $tenant->id, 'received_at' => now()->subDays(10)]);

    $key = makeApiKey($tenant->id, $user->id);
    $since = now()->subDays(2)->toDateString();

    $r = $this->getJson("/api/v1/deals?since={$since}", ['Authorization' => "Bearer {$key}"]);

    $r->assertOk();
    expect($r->json('data'))->toHaveCount(1);
});

test('невалидный since → 422', function () {
    $tenant = Tenant::factory()->create();
    $user = User::factory()->create(['tenant_id' => $tenant->id]);
    $key = makeApiKey($tenant->id, $user->id);

    $this->getJson('/api/v1/deals?since=не-дата-вовсе', ['Authorization' => "Bearer {$key}"])
        ->assertStatus(422);
});

test('невалидный cursor → 422', function () {
    $tenant = Tenant::factory()->create();
    $user = User::factory()->create(['tenant_id' => $tenant->id]);
    $key = makeApiKey($tenant->id, $user->id);

    // Валидный base64, но не JSON с ключами r/i → 422.
    $bad = base64_encode('просто строка');
    $this->getJson("/api/v1/deals?cursor={$bad}", ['Authorization' => "Bearer {$key}"])
        ->assertStatus(422);
});

test('ключ без scope read → 403', function () {
    $tenant = Tenant::factory()->create();
    $user = User::factory()->create(['tenant_id' => $tenant->id]);
    $key = makeApiKey($tenant->id, $user->id, ['scopes' => ['write']]);

    $this->getJson('/api/v1/deals', ['Authorization' => "Bearer {$key}"])
        ->assertStatus(403);
});

test('keyset-пагинация: limit=1 → next_cursor → вторая страница без перекрытия', function () {
    $tenant = Tenant::factory()->create();
    $user = User::factory()->create(['tenant_id' => $tenant->id]);

    Deal::factory()->create(['tenant_id' => $tenant->id, 'received_at' => now()->subMinutes(1)]);
    Deal::factory()->create(['tenant_id' => $tenant->id, 'received_at' => now()->subMinutes(2)]);
    Deal::factory()->create(['tenant_id' => $tenant->id, 'received_at' => now()->subMinutes(3)]);

    $key = makeApiKey($tenant->id, $user->id);
    $auth = ['Authorization' => "Bearer {$key}"];

    $p1 = $this->getJson('/api/v1/deals?limit=1', $auth);
    $p1->assertOk();
    expect($p1->json('data'))->toHaveCount(1);
    $cursor = $p1->json('next_cursor');
    expect($cursor)->not->toBeNull();
    $firstId = $p1->json('data.0.id');

    $p2 = $this->getJson("/api/v1/deals?limit=1&cursor={$cursor}", $auth);
    $p2->assertOk();
    expect($p2->json('data'))->toHaveCount(1);
    // Вторая страница не повторяет первую (keyset, не offset).
    expect($p2->json('data.0.id'))->not->toBe($firstId);
});
