<?php

declare(strict_types=1);

use Builder\Core\Tests\BuilderTestUser;
use Illuminate\Support\Facades\Schema;
use Inertia\Testing\AssertableInertia;
use PragmaRX\Google2FA\Google2FA;

beforeEach(function (): void {
    Schema::create('users', function ($table): void {
        $table->id();
        $table->string('name')->nullable();
        $table->string('email')->nullable()->unique();
        $table->string('password')->nullable();
        $table->text('two_factor_secret')->nullable();
        $table->text('two_factor_recovery_codes')->nullable();
        $table->timestamp('two_factor_confirmed_at')->nullable();
        $table->rememberToken();
        $table->timestamps();
    });

    config(['auth.providers.users.model' => BuilderTestUser::class]);
});

it('GET /mfa renders the MFA challenge page', function (): void {
    $user = BuilderTestUser::create([
        'name' => 'Jane',
        'email' => 'jane@example.com',
        'password' => 'secret',
    ]);

    $this->actingAs($user)
        ->get(route('builder.mfa.challenge'))
        ->assertInertia(fn (AssertableInertia $page): AssertableInertia => $page
            ->component('Auth/MfaChallenge'));
});

it('POST /mfa with valid TOTP code sets mfa_verified and redirects to dashboard', function (): void {
    $google2fa = new Google2FA;
    $secret = $google2fa->generateSecretKey();

    $user = BuilderTestUser::create([
        'name' => 'Jane',
        'email' => 'jane@example.com',
        'password' => 'secret',
        'two_factor_secret' => encrypt($secret),
        'two_factor_recovery_codes' => encrypt(json_encode(['AAAA-BBBB'])),
        'two_factor_confirmed_at' => now(),
    ]);

    $code = $google2fa->getCurrentOtp($secret);

    $this->actingAs($user)
        ->post(route('builder.mfa.challenge.store'), ['code' => $code])
        ->assertRedirect(route('builder.dashboard'));

    expect(session('mfa_verified'))->toBeTrue();
});

it('POST /mfa with valid recovery code removes it and redirects', function (): void {
    $user = BuilderTestUser::create([
        'name' => 'Jane',
        'email' => 'jane@example.com',
        'password' => 'secret',
        'two_factor_recovery_codes' => encrypt(json_encode(['ABCD-1234', 'EFGH-5678'])),
        'two_factor_confirmed_at' => now(),
    ]);

    $this->actingAs($user)
        ->post(route('builder.mfa.challenge.store'), ['code' => 'ABCD-1234'])
        ->assertRedirect(route('builder.dashboard'));

    $user->refresh();
    $remaining = json_decode(decrypt($user->two_factor_recovery_codes), true);

    expect($remaining)->not->toContain('ABCD-1234')
        ->and($remaining)->toContain('EFGH-5678');
});

it('POST /mfa with corrupted recovery_codes JSON returns error (not 500)', function (): void {
    // Recovery codes column contains valid encrypted blob whose plaintext is
    // *not* a JSON array. Without a guard this hits array_map(..., null) =
    // TypeError = 500. Behaviour must be graceful: treat as no recovery codes
    // available and fall through to the generic invalid-code error.
    $user = BuilderTestUser::create([
        'name' => 'Jane',
        'email' => 'jane@example.com',
        'password' => 'secret',
        'two_factor_recovery_codes' => encrypt('this-is-not-json'),
        'two_factor_confirmed_at' => now(),
    ]);

    $this->actingAs($user)
        ->post(route('builder.mfa.challenge.store'), ['code' => 'ABCD-1234'])
        ->assertSessionHasErrors(['code']);
});

it('POST /mfa rejects a non-numeric short code without hitting verify', function (): void {
    $user = BuilderTestUser::create([
        'name' => 'Jane',
        'email' => 'jane@example.com',
        'password' => 'secret',
        'two_factor_recovery_codes' => encrypt(json_encode(['ABCD-1234'])),
        'two_factor_confirmed_at' => now(),
    ]);

    $this->actingAs($user)
        ->post(route('builder.mfa.challenge.store'), ['code' => 'abc'])
        ->assertSessionHasErrors(['code']);
});

it('POST /mfa rejects oversized code payload via validation', function (): void {
    $user = BuilderTestUser::create([
        'name' => 'Jane',
        'email' => 'jane@example.com',
        'password' => 'secret',
        'two_factor_recovery_codes' => encrypt(json_encode(['ABCD-1234'])),
        'two_factor_confirmed_at' => now(),
    ]);

    $this->actingAs($user)
        ->post(route('builder.mfa.challenge.store'), ['code' => str_repeat('A', 200)])
        ->assertSessionHasErrors(['code']);
});

it('POST /mfa with a valid recovery code twice consumes it only once', function (): void {
    $user = BuilderTestUser::create([
        'name' => 'Jane',
        'email' => 'jane@example.com',
        'password' => 'secret',
        'two_factor_recovery_codes' => encrypt(json_encode(['ABCD-1234', 'EFGH-5678'])),
        'two_factor_confirmed_at' => now(),
    ]);

    $this->actingAs($user)
        ->post(route('builder.mfa.challenge.store'), ['code' => 'ABCD-1234'])
        ->assertRedirect(route('builder.dashboard'));

    // Second submission with the same code must fail — first call consumed it.
    $this->actingAs($user->fresh())
        ->post(route('builder.mfa.challenge.store'), ['code' => 'ABCD-1234'])
        ->assertSessionHasErrors(['code']);
});

it('POST /mfa with invalid code returns error', function (): void {
    $google2fa = new Google2FA;
    $secret = $google2fa->generateSecretKey();

    $user = BuilderTestUser::create([
        'name' => 'Jane',
        'email' => 'jane@example.com',
        'password' => 'secret',
        'two_factor_secret' => encrypt($secret),
        'two_factor_recovery_codes' => encrypt(json_encode(['ZZZZ-9999'])),
        'two_factor_confirmed_at' => now(),
    ]);

    // '000000' will almost certainly not match the current OTP
    $this->actingAs($user)
        ->post(route('builder.mfa.challenge.store'), ['code' => 'INVALID-CODE'])
        ->assertSessionHasErrors(['code']);
});
