<?php

namespace App\Providers;

use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use App\Services\UserLookupService;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable;
use Laravel\Fortify\Fortify;

class FortifyServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Fortify::createUsersUsing(CreateNewUser::class);
        Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
        Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
        Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
        Fortify::redirectUserForTwoFactorAuthenticationUsing(RedirectIfTwoFactorAuthenticatable::class);

        // Lets the "email" field (Fortify's `username()` config, kept as 'email' to
        // avoid touching the rest of the pipeline) hold either an email or the app's
        // own `username` column, and rejects login outright (no session) for accounts
        // that haven't verified their email yet. NOTE: Fortify's own
        // RedirectIfTwoFactorAuthenticatable action calls this callback a second time
        // (to decide whether to redirect to the 2FA challenge) before
        // AttemptToAuthenticate calls it again to log the user in — keep this
        // side-effect-free (read-only lookup + hash check only).
        Fortify::authenticateUsing(function (Request $request) {
            if (method_exists(auth()->guard(), 'setRememberDuration')) {
                auth()->guard()->setRememberDuration(43200); // 30 days in minutes (30 * 24 * 60)
            }

            $user = app(UserLookupService::class)->findByLogin($request->input('email'));

            if (!$user || !Hash::check($request->password, $user->password)) {
                return null; // Fortify throws its own generic "these credentials do not match".
            }

            if (!$user->hasVerifiedEmail()) {
                throw ValidationException::withMessages([
                    Fortify::username() => ['Please verify your email address before logging in. Check your inbox for a confirmation link or 6-digit code.'],
                ]);
            }

            return $user;
        });

        RateLimiter::for('login', function (Request $request) {
            $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());

            return Limit::perMinute(5)->by($throttleKey);
        });

        RateLimiter::for('two-factor', function (Request $request) {
            return Limit::perMinute(5)->by($request->session()->get('login.id'));
        });

        RateLimiter::for('verification-resend', function (Request $request) {
            return Limit::perHour(6)->by(Str::lower((string) $request->input('login')));
        });
    }
}
