<?php

declare(strict_types=1);

use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use PragmaRX\Google2FA\Google2FA;

uses(DatabaseTransactions::class);

beforeEach(function () {
    $this->tenant = Tenant::factory()->create();
    // CACHE_STORE=array, но процесс PHPUnit один — на всякий случай чистим.
    RateLimiter::clear('auth:login:locked@example.ru|127.0.0.1');
});

test('login: после 5 неуспешных попыток 6-я возвращает 429 + Retry-After', function () {
    User::factory()->create([
        'tenant_id' => $this->tenant->id,
        'email' => 'locked@example.ru',
        'password_hash' => Hash::make('right-pass'),
    ]);

    // 5 неудач → каждая 422.
    for ($i = 1; $i <= 5; $i++) {
        $r = $this->postJson('/api/auth/login', [
            'email' => 'locked@example.ru',
            'password' => 'wrong-pass-'.$i,
        ]);
        expect($r->status())->toBe(422);
    }

    // 6-я с правильным паролем — всё равно 429 (lockout).
    $r = $this->postJson('/api/auth/login', [
        'email' => 'locked@example.ru',
        'password' => 'right-pass',
    ]);

    $r->assertStatus(429);
    expect($r->headers->get('Retry-After'))->not->toBeNull();
    expect((int) $r->headers->get('Retry-After'))->toBeGreaterThan(0);
    expect((int) $r->headers->get('Retry-After'))->toBeLessThanOrEqual(900);
    expect($r->json('retry_after'))->toBeGreaterThan(0);
    expect($r->json('message'))->toContain('Слишком много попыток');
});

test('login: успешный вход чистит throttle-счётчик', function () {
    User::factory()->create([
        'tenant_id' => $this->tenant->id,
        'email' => 'success@example.ru',
        'password_hash' => Hash::make('right-pass'),
    ]);

    // 4 неудачи (под лимитом).
    for ($i = 1; $i <= 4; $i++) {
        $this->postJson('/api/auth/login', [
            'email' => 'success@example.ru',
            'password' => 'wrong-'.$i,
        ])->assertStatus(422);
    }

    // 5-я — успешный → счётчик чистится.
    $this->postJson('/api/auth/login', [
        'email' => 'success@example.ru',
        'password' => 'right-pass',
    ])->assertOk();

    // Logout чтобы не мешать следующему login (Pest cookie-jar).
    $this->postJson('/api/auth/logout');

    // 5 новых неудач должны проходить (а не сразу падать в 429).
    for ($i = 1; $i <= 5; $i++) {
        $r = $this->postJson('/api/auth/login', [
            'email' => 'success@example.ru',
            'password' => 'still-wrong-'.$i,
        ]);
        expect($r->status())->toBe(422);
    }

    // А вот 6-я уже должна быть 429.
    $this->postJson('/api/auth/login', [
        'email' => 'success@example.ru',
        'password' => 'still-wrong-final',
    ])->assertStatus(429);
});

test('login: throttle ключ изолирован по email — два разных user не блокируют друг друга', function () {
    User::factory()->create([
        'tenant_id' => $this->tenant->id,
        'email' => 'alice@example.ru',
        'password_hash' => Hash::make('alice-pass'),
    ]);
    User::factory()->create([
        'tenant_id' => $this->tenant->id,
        'email' => 'bob@example.ru',
        'password_hash' => Hash::make('bob-pass'),
    ]);

    // Alice — 5 неудач.
    for ($i = 1; $i <= 5; $i++) {
        $this->postJson('/api/auth/login', [
            'email' => 'alice@example.ru',
            'password' => 'wrong-pass-attempt',
        ])->assertStatus(422);
    }
    $aliceKey = 'auth:login:alice@example.ru|127.0.0.1';
    expect(RateLimiter::attempts($aliceKey))->toBe(5);
    expect(RateLimiter::tooManyAttempts($aliceKey, 5))->toBeTrue();

    // 6-я попытка Alice (даже с правильным паролем) → 429.
    $this->postJson('/api/auth/login', [
        'email' => 'alice@example.ru',
        'password' => 'alice-pass',
    ])->assertStatus(429);

    // Bob — нет.
    $this->postJson('/api/auth/login', [
        'email' => 'bob@example.ru',
        'password' => 'bob-pass',
    ])->assertOk();
});

test('login: account_locked (is_active=false) тоже расходует попытки', function () {
    User::factory()->create([
        'tenant_id' => $this->tenant->id,
        'email' => 'inactive@example.ru',
        'password_hash' => Hash::make('right-pass'),
        'is_active' => false,
    ]);

    // 5 раз получаем 422 «Аккаунт заблокирован».
    for ($i = 1; $i <= 5; $i++) {
        $this->postJson('/api/auth/login', [
            'email' => 'inactive@example.ru',
            'password' => 'right-pass',
        ])->assertStatus(422);
    }

    // 6-я → 429.
    $this->postJson('/api/auth/login', [
        'email' => 'inactive@example.ru',
        'password' => 'right-pass',
    ])->assertStatus(429);
});

test('2FA verify: после 5 неверных кодов 6-й возвращает 429', function () {
    $google2fa = new Google2FA;
    $secret = $google2fa->generateSecretKey();

    User::factory()->create([
        'tenant_id' => $this->tenant->id,
        'email' => '2fa-locked@example.ru',
        'password_hash' => Hash::make('right-pass'),
        'totp_enabled' => true,
        'totp_secret' => $secret,
    ]);

    // Login → pending_user_id в session (Pest cookie-jar держит её между запросами).
    $this->postJson('/api/auth/login', [
        'email' => '2fa-locked@example.ru',
        'password' => 'right-pass',
    ])->assertOk()->assertJsonPath('requires_2fa', true);

    // 5 неверных кодов.
    for ($i = 0; $i < 5; $i++) {
        $r = $this->postJson('/api/auth/2fa/verify', [
            'code' => '000000',
        ]);
        expect($r->status())->toBe(422);
    }

    // 6-я попытка даже с правильным TOTP — 429.
    $validCode = $google2fa->getCurrentOtp($secret);
    $r = $this->postJson('/api/auth/2fa/verify', [
        'code' => $validCode,
    ]);

    $r->assertStatus(429);
    expect($r->headers->get('Retry-After'))->not->toBeNull();
});

test('2FA verify: успех чистит throttle', function () {
    $google2fa = new Google2FA;
    $secret = $google2fa->generateSecretKey();

    User::factory()->create([
        'tenant_id' => $this->tenant->id,
        'email' => '2fa-success@example.ru',
        'password_hash' => Hash::make('right-pass'),
        'totp_enabled' => true,
        'totp_secret' => $secret,
    ]);

    $this->postJson('/api/auth/login', [
        'email' => '2fa-success@example.ru',
        'password' => 'right-pass',
    ])->assertOk();

    // 3 неудачи (под лимитом).
    for ($i = 0; $i < 3; $i++) {
        $this->postJson('/api/auth/2fa/verify', ['code' => '000000'])
            ->assertStatus(422);
    }

    // Успех.
    $this->postJson('/api/auth/2fa/verify', [
        'code' => $google2fa->getCurrentOtp($secret),
    ])->assertOk();
});
