<?php

namespace App\Livewire\Auth;

use App\Livewire\Concerns\CompletesTwoFactorChallenge;
use App\Models\User;
use App\Services\WebauthnService;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Validate;
use Livewire\Component;

#[Layout('layouts.auth')]
class TwoFactorChallenge extends Component
{
    use CompletesTwoFactorChallenge;

    #[Validate('required|string')]
    public string $code = '';

    public function mount()
    {
        if (! session()->has('2fa.user')) {
            return $this->redirect(route('login'), navigate: true);
        }

        // No TOTP and webauthnAvailable() is false (no registered key, or — for a key-only user —
        // no secure context on http + bare IP) → the backup code is the only path: go to its view.
        if (! $this->pendingHasTotp && ! $this->webauthnAvailable()) {
            return $this->redirect(route('two-factor.challenge.backup'), navigate: true);
        }
    }

    public function verify()
    {
        $this->validate();

        $this->assertNotRateLimited();

        $user = $this->pendingUser();

        if (! $user || ! $user->hasTwoFactorEnabled()) {
            session()->forget(['2fa.user', '2fa.remember']);
            throw ValidationException::withMessages(['code' => __('auth.session_expired')]);
        }

        // Accept the TOTP code only when the pending user actually has TOTP (otherwise a null
        // secret would break verifyKey); always allow a one-time backup (recovery) code.
        $valid = $user->verifyTotp($this->code) || $user->useRecoveryCode($this->code);

        if (! $valid) {
            $this->recordFailedAttempt('2fa');
            throw ValidationException::withMessages(['code' => __('auth.invalid_code')]);
        }

        $this->clearRateLimit();

        return $this->completeLogin($user);
    }

    /** JSON request options for navigator.credentials.get — the JS posts the result to verifyWebauthn. */
    public function assertionOptions(WebauthnService $webauthn): array
    {
        $user = User::find(session('2fa.user'));
        abort_unless($user && $webauthn->available() && $user->hasWebauthnCredentials(), 404);

        return $webauthn->assertionOptions($user);
    }

    /** Complete the login with a verified security-key assertion (alternative to the TOTP/backup code). */
    public function verifyWebauthn(array $assertion, WebauthnService $webauthn)
    {
        $this->assertNotRateLimited();

        $user = User::find(session('2fa.user'));

        if (! $user || ! $user->hasTwoFactorEnabled() || ! $webauthn->available() || ! $webauthn->verifyAssertion($user, $assertion)) {
            $this->recordFailedAttempt('2fa');
            throw ValidationException::withMessages(['code' => __('auth.webauthn_failed')]);
        }

        $this->clearRateLimit();

        return $this->completeLogin($user);
    }

    public function render()
    {
        return view('livewire.auth.two-factor-challenge')->title(__('auth.title_challenge'));
    }
}
