<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\AuditService;
use App\Services\LoginAttemptService;
use App\Services\TotpService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

class TwoFactorChallengeController extends Controller
{
    public function __construct(
        private readonly TotpService $totp,
        private readonly LoginAttemptService $attempts,
    ) {}

    public function create(Request $request): View|RedirectResponse
    {
        if (! $request->session()->has('security.two_factor_user_id')) {
            return redirect()->route('login');
        }

        return view('auth.two-factor-challenge');
    }

    public function store(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'code' => ['required', 'string', 'max:64'],
        ]);

        $userId = (int) $request->session()->get('security.two_factor_user_id', 0);
        $user = User::query()
            ->whereKey($userId)
            ->where('status', 'active')
            ->whereNotNull('two_factor_confirmed_at')
            ->first();

        if (! $user) {
            $request->session()->forget(['security.two_factor_user_id', 'security.two_factor_remember']);

            return redirect()->route('login')->withErrors(['login' => 'Please sign in again.']);
        }

        $key = 'two-factor:'.$user->getKey().'|'.$request->ip();
        if (RateLimiter::tooManyAttempts($key, 5)) {
            $this->attempts->record($request, 'two_factor_throttled', $user->username ?: $user->email, $user);
            throw ValidationException::withMessages(['code' => 'Too many verification attempts. Try again shortly.']);
        }

        $code = trim($data['code']);
        $validTotp = filled($user->two_factor_secret) && $this->totp->verify((string) $user->two_factor_secret, $code);
        $usedRecovery = false;

        if (! $validTotp) {
            $usedRecovery = $this->totp->consumeRecoveryCode($user, $code);
        }

        if (! $validTotp && ! $usedRecovery) {
            RateLimiter::hit($key, 60);
            $this->attempts->record($request, 'failed_two_factor', $user->username ?: $user->email, $user);
            throw ValidationException::withMessages(['code' => 'The verification code is invalid.']);
        }

        RateLimiter::clear($key);
        $remember = (bool) $request->session()->pull('security.two_factor_remember', false);
        $request->session()->forget('security.two_factor_user_id');
        Auth::login($user, $remember);
        $request->session()->regenerate();

        $status = $usedRecovery ? 'recovery_success' : 'two_factor_success';
        $this->attempts->record($request, $status, $user->username ?: $user->email, $user);
        AuditService::record('auth.two_factor_verified', $user, [], ['method' => $usedRecovery ? 'recovery_code' : 'totp']);

        return redirect()->intended(route('dashboard'));
    }
}
