<?php

namespace Tests\Feature;

use App\Livewire\Auth\Login;
use App\Livewire\Auth\TwoFactorChallenge;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
use Livewire\Livewire;
use PragmaRX\Google2FAQRCode\Google2FA;
use Tests\TestCase;

class BruteforceHooksTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        Cache::flush();
        Setting::put('bruteforce_maxretry', '3');
    }

    /**
     * Drive request()->ip() to a public IP for every Livewire request in this test.
     *
     * Livewire's RequestBroker creates a fresh Symfony request (REMOTE_ADDR=127.0.0.1)
     * for every HTTP call, then the kernel binds it via app()->instance('request', $req).
     * The rebinding callback fires on every such binding, letting us mutate the already-
     * created request before component code reads request()->ip().
     *
     * The app container is re-created between test methods (RefreshDatabase + setUp
     * boot a fresh app each time), so rebinding callbacks do not leak across tests.
     */
    private function spoofIp(string $ip): void
    {
        app()->rebinding('request', static function ($app, $request) use ($ip) {
            $request->server->set('REMOTE_ADDR', $ip);
        });
    }

    public function test_failed_logins_ban_the_ip_at_threshold(): void
    {
        User::factory()->create(['email' => 'admin@clusev.local', 'password' => Hash::make('correct-horse')]);

        // Drive request()->ip() to a PUBLIC IP (203.0.113.5, RFC-5737 TEST-NET).
        // 127.0.0.1 is hard-exempt in BruteforceGuard; using a loopback IP would make
        // record() no-op and the ban assertion would never hold.
        $this->spoofIp('203.0.113.5');

        for ($i = 0; $i < 3; $i++) {
            Livewire::test(Login::class)
                ->set('email', 'admin@clusev.local')
                ->set('password', 'wrong')
                ->call('authenticate');
        }

        $this->assertDatabaseHas('banned_ips', ['ip' => '203.0.113.5', 'reason' => 'login']);
        $this->assertDatabaseHas('audit_events', ['action' => 'auth.login_failed']);
    }

    public function test_failed_2fa_codes_ban_the_ip(): void
    {
        $user = User::factory()->create([
            'two_factor_secret' => (new Google2FA)->generateSecretKey(),
            'two_factor_confirmed_at' => now(),
        ]);

        // 203.0.113.6 — separate public IP to avoid cross-test cache collisions.
        // maxretry=3 (setUp) < 2FA per-(user+IP) rate-limit (5), so ban triggers first.
        $this->spoofIp('203.0.113.6');

        for ($i = 0; $i < 3; $i++) {
            session()->put('2fa.user', $user->id);
            Livewire::test(TwoFactorChallenge::class)
                ->set('code', '000000')
                ->call('verify');
        }

        $this->assertDatabaseHas('banned_ips', ['ip' => '203.0.113.6', 'reason' => '2fa']);
        $this->assertDatabaseHas('audit_events', ['action' => 'auth.2fa_failed']);
    }
}
