<?php

namespace App\Livewire\Modals;

use App\Models\AuditEvent;
use Illuminate\Support\Facades\Auth;
use LivewireUI\Modal\ModalComponent;

/**
 * Backup (recovery) codes, shown once in a modal. Opened on first-factor enrollment and
 * from Settings → Security ("Backup-Codes verwalten"). The download endpoint stays a route.
 *
 * Show-once is structural, not UX-level: the component holds NO codes and NO "revealed"
 * state. Codes reach the browser only via a transient `reveal-codes` event fired from
 * mount() (one-time `2fa.codes_fresh` flag) or regenerate(). Because mount() never runs on
 * hydrate and dispatched events live in the one-time response effects (never the snapshot),
 * a replayed/stale snapshot has nothing to re-render and fires no event — captured codes
 * cannot be re-revealed by replaying a signed snapshot.
 */
class RecoveryCodes extends ModalComponent
{
    public static function modalMaxWidth(): string
    {
        return 'lg';
    }

    public function mount(): void
    {
        // One-time, server-set flag (a timestamp put on first-factor enrollment). pull() reads
        // + forgets, so a refresh of the manage view will no longer reveal the codes. mount()
        // does not run on hydrate, so a replayed snapshot never re-triggers this. The flag is
        // valid only within [now-600s, now]: a stale flag (enrollment whose modal never opened)
        // or a non-timestamp value reveals nothing.
        $freshAt = session()->pull('2fa.codes_fresh');
        $age = is_int($freshAt) ? now()->timestamp - $freshAt : null;
        if ($age !== null && $age >= 0 && $age <= 600) {
            $this->dispatchCodes();
        }
    }

    /** Regenerate the current user's backup codes (invalidates the old set). */
    public function regenerate(): void
    {
        Auth::user()->replaceRecoveryCodes();

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

        // One-time download grant (a timestamp) for the freshly regenerated set, consumed by
        // the download route within a short window. Without it a later direct hit 403s.
        session()->put('2fa.download_grant', now()->timestamp);

        // Reveal the freshly generated set once, via the transient event (never a property).
        $this->dispatchCodes();

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

    public function render()
    {
        return view('livewire.modals.recovery-codes');
    }

    /**
     * Deliver the current user's recovery codes to the browser via a one-time event. The
     * codes never enter a Livewire property or the snapshot — Alpine holds them in JS memory
     * only, so a replayed snapshot cannot re-render them.
     */
    protected function dispatchCodes(): void
    {
        $this->dispatch('reveal-codes', codes: Auth::user()->recoveryCodes());
    }
}
