<?php

namespace Tests\Feature\Modals;

use App\Livewire\Modals\ConfirmAction;
use App\Models\AuditEvent;
use App\Models\Server;
use App\Models\User;
use App\Support\Confirm\ConfirmToken;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Features\SupportLockedProperties\CannotUpdateLockedPropertyException;
use Livewire\Livewire;
use Tests\TestCase;

class ConfirmActionTest extends TestCase
{
    use RefreshDatabase;

    private function login(): User
    {
        $user = User::factory()->create();
        $this->actingAs($user);

        return $user;
    }

    public function test_confirm_with_a_valid_token_audits_from_the_payload_and_dispatches_the_event(): void
    {
        $this->login();
        $server = Server::create(['name' => 'box', 'ip' => '10.0.0.9', 'ssh_port' => 22, 'status' => 'online']);
        $token = ConfirmToken::issue('keyRemoved', ['fingerprint' => 'ab:cd'], 'ssh_key.remove', 'box · key', $server->id);

        Livewire::test(ConfirmAction::class, ['token' => $token])
            ->call('confirm')
            ->assertDispatched('keyRemoved', confirmToken: $token);

        $this->assertSame(1, AuditEvent::where('action', 'ssh_key.remove')->count());
        $this->assertDatabaseHas('audit_events', [
            'action' => 'ssh_key.remove',
            'target' => 'box · key',
            'server_id' => $server->id,
        ]);
    }

    public function test_confirm_on_a_deferred_flow_dispatches_without_writing_audit(): void
    {
        $this->login();
        // No auditAction → the handler audits after the remote op; confirm() must not.
        $token = ConfirmToken::issue('firewallRuleDelete', ['spec' => '22/tcp', 'label' => 'SSH']);

        Livewire::test(ConfirmAction::class, ['token' => $token])
            ->call('confirm')
            ->assertDispatched('firewallRuleDelete', confirmToken: $token);

        $this->assertSame(0, AuditEvent::count());
    }

    public function test_confirm_with_a_forged_token_is_rejected_no_audit_no_dispatch(): void
    {
        $this->login();

        Livewire::test(ConfirmAction::class, ['token' => 'forged-token'])
            ->call('confirm')
            ->assertDispatched('notify', message: __('modals.confirm_action.rejected'))
            ->assertNotDispatched('keyRemoved');

        $this->assertSame(0, AuditEvent::count());
    }

    public function test_confirm_cannot_be_replayed_after_the_handler_burned_the_token(): void
    {
        $this->login();
        $token = ConfirmToken::issue('twoFactorDisabled', [], 'two_factor.disable', 'a@b.c');

        // Simulate the full lifecycle: modal confirmed it, handler burned it.
        ConfirmToken::confirm($token);
        ConfirmToken::consume($token, 'twoFactorDisabled');

        Livewire::test(ConfirmAction::class, ['token' => $token])
            ->call('confirm')
            ->assertDispatched('notify', message: __('modals.confirm_action.rejected'))
            ->assertNotDispatched('twoFactorDisabled');

        $this->assertSame(0, AuditEvent::count());
    }

    public function test_the_routing_token_is_locked_against_client_tampering(): void
    {
        $this->login();
        $token = ConfirmToken::issue('twoFactorDisabled');

        $this->expectException(CannotUpdateLockedPropertyException::class);

        Livewire::test(ConfirmAction::class, ['token' => $token])
            ->set('token', ConfirmToken::issue('userRemoved', ['userId' => 1]));
    }

    public function test_the_display_target_comes_from_the_signed_token(): void
    {
        $this->login();
        $token = ConfirmToken::issue('keyRemoved', [], 'ssh_key.remove', 'webserver · deploy@host', 7);

        Livewire::test(ConfirmAction::class, ['token' => $token])
            ->assertSee('webserver · deploy@host');
    }
}
