<?php

namespace Tests\Feature;

use App\Livewire\Auth\TwoFactorSetup;
use App\Livewire\Settings\WebauthnKeys;
use App\Models\User;
use App\Models\WebauthnCredential;
use App\Services\WebauthnService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Mockery;
use PragmaRX\Google2FAQRCode\Google2FA;
use Tests\TestCase;

class FirstFactorCodesTest extends TestCase
{
    use RefreshDatabase;

    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }

    public function test_first_totp_confirm_generates_codes_and_opens_modal(): void
    {
        $user = User::factory()->create();
        $g = new Google2FA;
        $secret = $g->generateSecretKey();

        Livewire::actingAs($user)->test(TwoFactorSetup::class)
            ->set('secret', $secret)
            ->set('code', $g->getCurrentOtp($secret))
            ->call('confirm')
            ->assertRedirect(route('settings'));

        $this->assertTrue($user->fresh()->hasRecoveryCodes());
        $this->assertTrue(session('open_recovery_modal'));
        // The modal that opens after the redirect reveals the fresh set once. The flag is a
        // timestamp (TTL-bounded) — the modal pulls it within its 10-minute window.
        $this->assertIsInt(session('2fa.codes_fresh'));
    }

    public function test_first_key_register_generates_codes_and_opens_modal(): void
    {
        $user = User::factory()->create();

        $svc = Mockery::mock(WebauthnService::class);
        $svc->shouldReceive('available')->andReturnTrue();
        $svc->shouldReceive('verifyRegistration')->andReturnUsing(fn ($u, $a, $name) => WebauthnCredential::create([
            'user_id' => $u->id, 'name' => $name, 'credential_id' => 'cid', 'public_key' => '{}', 'sign_count' => 0,
        ]));
        app()->instance(WebauthnService::class, $svc);

        Livewire::actingAs($user)->test(WebauthnKeys::class)
            ->set('newName', 'YubiKey 5C')
            ->call('register', ['dummy' => true], $svc)
            ->assertDispatched('openModal');

        $this->assertTrue($user->fresh()->hasRecoveryCodes());
        // The opening modal reveals the fresh set once. The flag is a TTL-bounded timestamp.
        $this->assertIsInt(session('2fa.codes_fresh'));
    }

    public function test_malformed_setup_secret_yields_validation_error_not_500(): void
    {
        $user = User::factory()->create();

        Livewire::actingAs($user)->test(TwoFactorSetup::class)
            ->set('secret', 'x') // client-tampered, too short for Google2FA
            ->set('code', '123456')
            ->call('confirm')
            ->assertHasErrors('code');

        $this->assertFalse($user->fresh()->hasTotp());
    }
}
