<?php

namespace App\Livewire\Settings;

use App\Models\AuditEvent;
use App\Services\WebauthnService;
use App\Support\Confirm\ConfirmToken;
use App\Support\Confirm\InvalidConfirmToken;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\On;
use Livewire\Attributes\Validate;
use Livewire\Component;

class WebauthnKeys extends Component
{
    #[Validate('required|string|max:60')]
    public string $newName = '';

    /** JSON creation options for navigator.credentials.create (the JS posts the result to register). */
    public function options(WebauthnService $webauthn): array
    {
        abort_unless($webauthn->available(), 404);
        // Validate the label BEFORE the browser ceremony, so an invalid name never
        // leaves an orphaned credential on the authenticator.
        $this->validate();

        return $webauthn->registrationOptions(Auth::user());
    }

    public function register(array $attestation, WebauthnService $webauthn): void
    {
        // WebAuthn must be available (domain + HTTPS). No existing-factor guard — a key may
        // be the FIRST/only factor.
        abort_unless($webauthn->available(), 404);
        $this->validate();

        $cred = $webauthn->verifyRegistration(Auth::user(), $attestation, trim($this->newName));
        $this->reset('newName');

        // First factor enrolled and no codes yet → generate + show the recovery modal.
        if (! Auth::user()->hasRecoveryCodes()) {
            Auth::user()->replaceRecoveryCodes();
            // Server-set one-time flag so the opening modal reveals the fresh set once.
            session()->put('2fa.codes_fresh', now()->timestamp);
            // One-time download grant (a timestamp) — the download route requires + consumes
            // it within a short window, so the fresh set can be downloaded once and a later
            // direct hit 403s.
            session()->put('2fa.download_grant', now()->timestamp);
            $this->dispatch('openModal', component: 'modals.recovery-codes');
        }

        AuditEvent::create([
            'user_id' => Auth::id(),
            'actor' => Auth::user()->name ?? 'system',
            'action' => 'webauthn.register',
            'target' => $cred->name,
            'ip' => request()->ip(),
        ]);

        $this->dispatch('notify', message: __('auth.webauthn_added'));
    }

    /** Destructive (R5): open the confirm modal, which re-dispatches `webauthnKeyRemoved`. */
    public function confirmRemove(int $id): void
    {
        $cred = Auth::user()->webauthnCredentials()->whereKey($id)->first();
        if (! $cred) {
            return;
        }

        $this->dispatch('openModal',
            component: 'modals.confirm-action',
            arguments: [
                'heading' => __('auth.webauthn_title'),
                'body' => __('auth.webauthn_remove_confirm'),
                'confirmLabel' => __('common.remove'),
                'danger' => true,
                'icon' => 'trash',
                'notify' => '',
                'token' => ConfirmToken::issue(
                    'webauthnKeyRemoved',
                    ['id' => $id],
                ),
            ],
        );
    }

    #[On('webauthnKeyRemoved')]
    public function remove(string $confirmToken, WebauthnService $webauthn): void
    {
        abort_unless($webauthn->available(), 404);

        try {
            $payload = ConfirmToken::consume($confirmToken, 'webauthnKeyRemoved');
        } catch (InvalidConfirmToken) {
            return; // forged / replayed / direct-bypass attempt — no-op
        }
        $id = $payload['params']['id'];

        $cred = Auth::user()->webauthnCredentials()->whereKey($id)->first();
        if ($cred) {
            $name = $cred->name;
            $cred->delete();

            AuditEvent::create([
                'user_id' => Auth::id(),
                'actor' => Auth::user()->name ?? 'system',
                'action' => 'webauthn.remove',
                'target' => $name,
                'ip' => request()->ip(),
            ]);

            $this->dispatch('notify', message: __('auth.webauthn_removed'));
            Auth::user()->resetIfNoFactor();
        }
    }

    public function render()
    {
        return view('livewire.settings.webauthn-keys', [
            'available' => app(WebauthnService::class)->available(),
            'keys' => Auth::user()->webauthnCredentials()->latest()->get(),
        ]);
    }
}
