<?php

namespace App\Livewire\Auth;

use App\Models\Operator;
use App\Support\AdminArea;
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\Component;

/**
 * The console's own sign-in.
 *
 * Not Fortify: Fortify binds to exactly one guard, and hanging both the portal
 * and the console off it would be the shared login this separation exists to
 * end. The two-factor MECHANICS are still Fortify's — the same
 * TwoFactorAuthenticatable trait and the same TOTP provider — only the flow is
 * ours.
 */
#[Layout('layouts.portal')]
class OperatorLogin extends Component
{
    public string $email = '';

    public string $password = '';

    public bool $remember = false;

    public function authenticate(): void
    {
        // Rules on the action: a #[Validate] attribute on a property applies
        // class-wide and would be dragged into any other action added later.
        //
        // password is NOT 'required': a failed attempt resets it below, and a
        // retry (or a brute-force loop) submitting on top of that empty value
        // must still count against the rate limiter instead of stopping short
        // at a validation error keyed 'password' — which would never accumulate
        // toward the throttle at all, five wrong-password submissions in a row
        // would never actually throttle. An empty password fails Hash::check()
        // like any other wrong one and gets the same generic message.
        $this->validate([
            'email' => ['required', 'email'],
            'password' => ['string'],
        ]);

        $key = 'operator-login:'.mb_strtolower($this->email).'|'.request()->ip();

        if (RateLimiter::tooManyAttempts($key, 5)) {
            throw ValidationException::withMessages([
                'email' => __('auth.throttle', ['seconds' => RateLimiter::availableIn($key)]),
            ]);
        }

        $operator = Operator::where('email', $this->email)->first();

        // One message for every failure — a different message for "no such
        // account" tells a stranger which addresses are operators.
        if ($operator === null
            || ! Hash::check($this->password, $operator->password)
            || ! $operator->isActive()
            || ! $operator->isOperator()) {
            RateLimiter::hit($key, 60);
            $this->reset('password');

            throw ValidationException::withMessages(['email' => __('auth.failed')]);
        }

        RateLimiter::clear($key);

        if ($operator->two_factor_confirmed_at !== null) {
            // Not signed in yet. The id is parked in the session and the guard
            // stays untouched until the code checks out.
            session(['operator.2fa.id' => $operator->id, 'operator.2fa.remember' => $this->remember]);
            $this->redirectRoute('admin.two-factor', navigate: false);

            return;
        }

        $this->completeLoginAndRedirect($operator, $this->remember);
    }

    /**
     * Shared with the two-factor challenge, so both exits behave identically.
     *
     * Re-checks isActive()/isOperator() here, not only at the password step:
     * an operator can be disabled, or lose every console role, in the window
     * between entering a password and completing a PENDING two-factor
     * challenge — an owner revoking access while someone is mid-login is the
     * realistic case, not a contrived one, and the challenge on its own only
     * ever confirmed the row still existed. One check, in the one place both
     * entry points already share, rather than a second copy in the challenge
     * that the next change forgets to keep in sync.
     *
     * @return bool False when the operator no longer qualifies: the login is
     *              refused, not completed — neither the guard nor the
     *              session is touched, and the caller decides how to say so.
     */
    public static function completeLogin(Operator $operator, bool $remember): bool
    {
        if (! $operator->isActive() || ! $operator->isOperator()) {
            return false;
        }

        Auth::guard('operator')->login($operator, $remember);
        session()->regenerate();
        $operator->forceFill(['last_login_at' => now()])->save();

        return true;
    }

    private function completeLoginAndRedirect(Operator $operator, bool $remember): void
    {
        if (! self::completeLogin($operator, $remember)) {
            throw ValidationException::withMessages(['email' => __('auth.failed')]);
        }

        $this->redirect(AdminArea::home(), navigate: false);
    }

    public function render()
    {
        return view('livewire.auth.operator-login');
    }
}
