<?php

use App\Livewire\Auth\OperatorTwoFactorChallenge;
use App\Models\Operator;
use App\Support\AdminArea;
use Illuminate\Support\Facades\Auth;
use Livewire\Livewire;
use PragmaRX\Google2FA\Google2FA;

/**
 * The operator's two-factor step, exercised directly.
 *
 * OperatorLoginTest proves the password step hands off to this challenge
 * without signing anyone in; these tests prove the challenge itself only
 * completes the sign-in once the code actually checks out — the other half of
 * "not signed in before the code is verified".
 */
function pendingOperator(array $attributes = []): Operator
{
    $operator = Operator::factory()->role('Owner')->create(array_merge([
        'password' => 'richtig-langes-pw',
        'two_factor_secret' => encrypt('JBSWY3DPEHPK3PXP'),
        'two_factor_recovery_codes' => encrypt(json_encode(['aaaa-1111', 'bbbb-2222'])),
        'two_factor_confirmed_at' => now(),
    ], $attributes));

    session(['operator.2fa.id' => $operator->id, 'operator.2fa.remember' => false]);

    return $operator;
}

it('is a dead end without a password step already taken', function () {
    Livewire::test(OperatorTwoFactorChallenge::class)->assertForbidden();

    expect(Auth::guard('operator')->check())->toBeFalse();
});

it('signs the operator in once the code actually checks out', function () {
    $operator = pendingOperator();
    $code = app(Google2FA::class)->getCurrentOtp(decrypt($operator->two_factor_secret));

    Livewire::test(OperatorTwoFactorChallenge::class)
        ->set('code', $code)
        ->call('verify')
        ->assertRedirect(AdminArea::home());

    expect(Auth::guard('operator')->check())->toBeTrue()
        ->and(session()->has('operator.2fa.id'))->toBeFalse();
});

it('refuses a wrong code and leaves the operator signed out', function () {
    $operator = pendingOperator();

    Livewire::test(OperatorTwoFactorChallenge::class)
        ->set('code', '000000')
        ->call('verify')
        ->assertHasErrors('code');

    expect(Auth::guard('operator')->check())->toBeFalse()
        ->and($operator->fresh()->last_login_at)->toBeNull();
});

it('accepts a recovery code exactly once', function () {
    $operator = pendingOperator();

    Livewire::test(OperatorTwoFactorChallenge::class)
        ->set('usingRecovery', true)
        ->set('recoveryCode', 'aaaa-1111')
        ->call('verify')
        ->assertRedirect(AdminArea::home());

    expect(Auth::guard('operator')->check())->toBeTrue();

    $codes = json_decode(decrypt($operator->fresh()->two_factor_recovery_codes), true);
    expect($codes)->not->toContain('aaaa-1111')
        ->and($codes)->toContain('bbbb-2222');

    // Spent — signing back out and trying the same code again must fail.
    Auth::guard('operator')->logout();
    session(['operator.2fa.id' => $operator->id, 'operator.2fa.remember' => false]);

    Livewire::test(OperatorTwoFactorChallenge::class)
        ->set('usingRecovery', true)
        ->set('recoveryCode', 'aaaa-1111')
        ->call('verify')
        ->assertHasErrors('code');

    expect(Auth::guard('operator')->check())->toBeFalse();
});

it('refuses to complete the login when the operator was disabled after the password step', function () {
    // The realistic case: an owner revokes access WHILE someone is mid-login,
    // not before. The code entered here is genuinely correct — the account
    // it belongs to no longer qualifies.
    $operator = pendingOperator();
    $code = app(Google2FA::class)->getCurrentOtp(decrypt($operator->two_factor_secret));

    $operator->forceFill(['disabled_at' => now()])->save();

    Livewire::test(OperatorTwoFactorChallenge::class)
        ->set('code', $code)
        ->call('verify')
        ->assertHasErrors('code');

    expect(Auth::guard('operator')->check())->toBeFalse()
        ->and($operator->fresh()->last_login_at)->toBeNull();
});

it('refuses to complete the login when every console role was revoked after the password step', function () {
    $operator = pendingOperator();
    $code = app(Google2FA::class)->getCurrentOtp(decrypt($operator->two_factor_secret));

    $operator->syncRoles([]);

    Livewire::test(OperatorTwoFactorChallenge::class)
        ->set('code', $code)
        ->call('verify')
        ->assertHasErrors('code');

    expect(Auth::guard('operator')->check())->toBeFalse()
        ->and($operator->fresh()->last_login_at)->toBeNull();
});

it('throttles repeated wrong codes', function () {
    pendingOperator();

    $component = Livewire::test(OperatorTwoFactorChallenge::class)->set('code', '000000');

    foreach (range(1, 5) as $ignored) {
        $component->call('verify');
    }

    $component->call('verify')->assertHasErrors('code');

    expect(Auth::guard('operator')->check())->toBeFalse();
});
