<?php

namespace Tests\Feature\Settings;

use App\Livewire\Settings\LoginProtection;
use App\Models\BannedIp;
use App\Models\Setting;
use App\Models\User;
use App\Support\Confirm\ConfirmToken;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Livewire\Livewire;
use Tests\TestCase;

class LoginProtectionTabTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        Cache::flush();
    }

    public function test_saving_persists_cast_settings(): void
    {
        $user = User::factory()->create();
        Livewire::actingAs($user)->test(LoginProtection::class)
            ->set('enabled', false)->set('maxretry', 7)->set('findtime', 15)->set('bantime', 120)
            ->set('whitelist', '203.0.113.0/24')
            ->call('save');

        $this->assertSame('0', Setting::get('bruteforce_enabled'));
        $this->assertSame('7', Setting::get('bruteforce_maxretry'));
        $this->assertSame('203.0.113.0/24', Setting::get('bruteforce_whitelist'));
    }

    public function test_invalid_whitelist_is_rejected(): void
    {
        $user = User::factory()->create();
        Livewire::actingAs($user)->test(LoginProtection::class)
            ->set('whitelist', 'junk-not-a-cidr')->call('save')
            ->assertHasErrors('whitelist');
        $this->assertNull(Setting::get('bruteforce_maxretry'));
    }

    public function test_table_lists_active_bans_and_unban_removes_one(): void
    {
        $user = User::factory()->create();
        BannedIp::create(['ip' => '203.0.113.5', 'banned_until' => now()->addHour(), 'attempts' => 10]);

        // Must authenticate before issuing so the token's uid matches Auth::id() at consume time.
        $this->actingAs($user);
        $token = ConfirmToken::issue('banCleared', ['ip' => '203.0.113.5']);
        ConfirmToken::confirm($token);
        Livewire::actingAs($user)->test(LoginProtection::class)
            ->assertSee('203.0.113.5')
            ->call('unban', $token);

        $this->assertDatabaseCount('banned_ips', 0);
        $this->assertDatabaseHas('audit_events', ['action' => 'auth.ip_unbanned', 'target' => '203.0.113.5']);
    }

    public function test_unban_all_clears_every_ban(): void
    {
        $user = User::factory()->create();
        BannedIp::create(['ip' => '203.0.113.5', 'banned_until' => now()->addHour(), 'attempts' => 10]);
        BannedIp::create(['ip' => '203.0.113.6', 'banned_until' => now()->addHour(), 'attempts' => 10]);

        $this->actingAs($user);
        $token = ConfirmToken::issue('bansClearedAll');
        ConfirmToken::confirm($token);
        Livewire::actingAs($user)->test(LoginProtection::class)->call('unbanAll', $token);

        $this->assertDatabaseCount('banned_ips', 0);
        $this->assertDatabaseHas('audit_events', ['action' => 'auth.ip_unbanned', 'target' => 'all']);
    }

    public function test_widening_whitelist_clears_now_exempt_bans(): void
    {
        $user = User::factory()->create();
        BannedIp::create(['ip' => '203.0.113.5', 'banned_until' => now()->addHour(), 'attempts' => 10]);

        Livewire::actingAs($user)->test(LoginProtection::class)
            ->set('whitelist', '203.0.113.0/24')->call('save');

        $this->assertDatabaseCount('banned_ips', 0); // 203.0.113.5 now exempt → purged
    }

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

        // Inject the public IP into every request bound to the container,
        // using the same rebinding mechanism as BruteforceHooksTest (Task 6).
        app()->rebinding('request', static function ($app, $request) {
            $request->server->set('REMOTE_ADDR', '198.51.100.7');
        });

        Livewire::actingAs($user)->test(LoginProtection::class)
            ->call('whitelistMyIp')
            ->assertSet('whitelist', fn ($w) => str_contains($w, '198.51.100.7'));
    }

    public function test_unban_rejects_a_forged_token(): void
    {
        $user = User::factory()->create();
        BannedIp::create(['ip' => '203.0.113.5', 'banned_until' => now()->addHour(), 'attempts' => 10]);

        Livewire::actingAs($user)->test(LoginProtection::class)->call('unban', 'not-a-real-token');

        $this->assertDatabaseCount('banned_ips', 1); // forged token → no-op, ban remains
    }
}
