<?php

declare(strict_types=1);

use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;

uses(DatabaseTransactions::class);

beforeEach(function () {
    $this->tenant = Tenant::factory()->create();
    $this->user = User::factory()->create([
        'tenant_id' => $this->tenant->id,
        'email' => 'recovery-user@example.ru',
        'password_hash' => Hash::make('right-password-1234'),
        'totp_enabled' => true,
        'totp_secret' => 'JBSWY3DPEHPK3PXP', // dummy
    ]);
    // 4 неиспользованных кодов + 1 использованный.
    $codes = ['ABCD-1234', 'ZZZZ-9999', 'qqqq-7777', 'XXXX-5555', 'used-code'];
    $this->plainCodes = ['abcd1234', 'zzzz9999', 'qqqq7777', 'xxxx5555', 'usedcode'];
    foreach ($codes as $i => $code) {
        DB::table('user_recovery_codes')->insert([
            'user_id' => $this->user->id,
            'code_hash' => Hash::make(mb_strtolower(str_replace('-', '', $code))),
            'used_at' => $i === 4 ? now() : null,
        ]);
    }
});

/**
 * Helper: эмулировать pending-2FA state (как после успешного login).
 */
function startPending(TestCase $self, string $email = 'recovery-user@example.ru'): void
{
    $self->postJson('/api/auth/login', [
        'email' => $email,
        'password' => 'right-password-1234',
    ])->assertOk()->assertJsonPath('requires_2fa', true);
}

test('POST /api/auth/2fa/recovery-use завершает login по правильному коду + помечает used + считает remaining', function () {
    startPending($this);

    $r = $this->postJson('/api/auth/2fa/recovery-use', [
        'code' => 'ABCD-1234',
    ]);

    $r->assertOk();
    expect($r->json('user.email'))->toBe('recovery-user@example.ru');
    expect($r->json('requires_2fa'))->toBeFalse();
    // Осталось 3 неиспользованных (4 минус первый, который мы только что use'нули).
    expect($r->json('recovery_codes_remaining'))->toBe(3);

    // Code marked as used.
    $usedCount = DB::table('user_recovery_codes')
        ->where('user_id', $this->user->id)
        ->whereNotNull('used_at')
        ->count();
    expect($usedCount)->toBe(2); // 1 был изначально + 1 новый.
});

test('POST /api/auth/2fa/recovery-use 422 при неверном коде', function () {
    startPending($this);

    $r = $this->postJson('/api/auth/2fa/recovery-use', [
        'code' => 'WRONG-CODE-9999',
    ]);

    $r->assertStatus(422);
    expect($r->json('errors.code'))->not->toBeEmpty();
});

test('POST /api/auth/2fa/recovery-use НЕ принимает уже использованный код', function () {
    startPending($this);

    $r = $this->postJson('/api/auth/2fa/recovery-use', [
        'code' => 'used-code',
    ]);

    $r->assertStatus(422);
});

test('POST /api/auth/2fa/recovery-use 422 без pending-сессии', function () {
    // Не делаем login — нет auth.pending_user_id в session.
    $r = $this->postJson('/api/auth/2fa/recovery-use', [
        'code' => 'ABCD-1234',
    ]);

    $r->assertStatus(422);
    expect($r->json('message'))->toContain('Сессия 2FA');
});

test('POST /api/auth/2fa/recovery-use принимает разные форматы (с дефисом, без, разный регистр)', function () {
    startPending($this);

    // 'ZZZZ-9999' нормализуется в 'zzzz9999' → совпадает с stored.
    $r = $this->postJson('/api/auth/2fa/recovery-use', [
        'code' => 'zzzz 9999', // пробел вместо дефиса
    ]);

    $r->assertOk();
});

test('POST /api/auth/2fa/recovery-use rate-limit: 5 неверных → 6-я = 429', function () {
    startPending($this);

    for ($i = 1; $i <= 5; $i++) {
        $this->postJson('/api/auth/2fa/recovery-use', [
            'code' => 'BAD-FAIL-'.$i,
        ])->assertStatus(422);
    }

    $r = $this->postJson('/api/auth/2fa/recovery-use', [
        'code' => 'ABCD-1234', // правильный, но уже locked.
    ]);

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