<?php

namespace App\Livewire\Auth;

use App\Models\AuditEvent;
use App\Models\User;
use App\Services\BruteforceGuard;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Validate;
use Livewire\Component;

#[Layout('layouts.auth')]
class Login extends Component
{
    /**
     * A fixed, valid cost-12 bcrypt hash. When the e-mail is unknown we still run one
     * Hash::check against this so the failure branch costs the same bcrypt as a known
     * account — closing the response-timing oracle that would otherwise reveal which
     * addresses exist (same class as the password-reset constant-time fix).
     */
    private const DUMMY_HASH = '$2y$12$6gKO7fsTd5ZoshlNsI7JpeuIU60K30r3K0mdPdIryNc2IQy8XwI.u';

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

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

    public bool $remember = false;

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

        // Three auto-expiring counters, none of which can permanently lock the control plane
        // (all decay in minutes; the host CLI `clusev:reset-admin` is always available):
        //  - pair  (email+IP): the tight 5/min bucket (unchanged).
        //  - ip    : caps one source hammering many accounts AND the bcrypt CPU it burns.
        //  - acct  : an IP-independent backstop so a DISTRIBUTED (multi-IP) brute-force of the
        //            single admin account is still capped instead of scaling linearly with IPs.
        $email = strtolower($this->email);
        $pairKey = 'login:'.md5($email.'|'.request()->ip());
        $ipKey = 'login-ip:'.md5(request()->ip());
        $acctKey = 'login-acct:'.md5($email);

        foreach ([[$pairKey, 5], [$ipKey, 20], [$acctKey, 30]] as [$k, $max]) {
            if (RateLimiter::tooManyAttempts($k, $max)) {
                throw ValidationException::withMessages([
                    'email' => __('auth.too_many_attempts', ['seconds' => RateLimiter::availableIn($k)]),
                ]);
            }
        }

        $user = User::where('email', $this->email)->first();

        // Evaluate the bcrypt FIRST (never short-circuit it): an unknown e-mail must still spend
        // one Hash::check against DUMMY_HASH, otherwise `! $user ||` would skip the hash and the
        // faster no-account response would re-open the enumeration timing oracle.
        $validPassword = Hash::check($this->password, $user?->password ?? self::DUMMY_HASH);

        if (! $user || ! $validPassword) {
            RateLimiter::hit($pairKey, 60);   // 5 / minute
            RateLimiter::hit($ipKey, 60);     // 20 / minute
            RateLimiter::hit($acctKey, 900);  // 30 / 15 minutes
            $ip = request()->ip();
            app(BruteforceGuard::class)->record($ip, 'login');
            AuditEvent::create([
                'user_id' => null,
                'actor' => 'system',
                'action' => 'auth.login_failed',
                'target' => $this->email,
                'ip' => $ip,
            ]);
            throw ValidationException::withMessages(['email' => __('auth.invalid_credentials')]);
        }

        // Only the tight pair bucket is cleared on success; the broader ip/acct counters
        // decay on their own so one valid login can't reset a flood's budget.
        RateLimiter::clear($pairKey);

        // 2FA gate: hold the login until the TOTP code is verified.
        if ($user->hasTwoFactorEnabled()) {
            session()->put('2fa.user', $user->id);
            session()->put('2fa.remember', $this->remember);

            return $this->redirect(route('two-factor.challenge'), navigate: true);
        }

        Auth::login($user, $this->remember);
        session()->regenerate();

        return $this->redirectIntended(route('dashboard'), navigate: true);
    }

    public function render()
    {
        return view('livewire.auth.login')->title(__('auth.title_login'));
    }
}
