<?php

namespace App\Services;

use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;

/**
 * Extracted from leolanka-inertia's AuthenticatedSessionController (see
 * docs/migration-plan.md §5) so the login-security logic — dummy-hash timing
 * defense, tenant-segmented rate limiting, and the 20-attempt/24h lockout —
 * is a single framework-agnostic call site shared by App\Filament\Pages\Auth\Login.
 */
class LoginThrottle
{
    /**
     * A real bcrypt hash checked when the email is unknown, so failed logins
     * take the same time whether or not the account exists.
     */
    public const DUMMY_HASH = '$2y$12$R3wmC/5gROdBN0xdpYKmGuWDGCRAwOJaBqmf5MF/1sgUl7bebAu1O';

    public function __construct(private readonly CurrentContext $context) {}

    public function burstKey(string $email, string $ip): string
    {
        return 'login:burst:'.$this->tenantSegment().Str::transliterate(Str::lower($email)).'|'.$ip;
    }

    public function lockoutKey(string $email): string
    {
        return 'login:lockout:'.$this->tenantSegment().Str::transliterate(Str::lower($email));
    }

    public function tooManyAttempts(string $burstKey): bool
    {
        return RateLimiter::tooManyAttempts($burstKey, 5);
    }

    public function availableInSeconds(string $burstKey): int
    {
        return RateLimiter::availableIn($burstKey);
    }

    /**
     * Timing-safe: always runs a bcrypt comparison, even for an unknown
     * email, against DUMMY_HASH.
     */
    public function passwordMatches(?User $user, string $password): bool
    {
        return Hash::check($password, $user->password ?? self::DUMMY_HASH);
    }

    public function recordFailure(string $burstKey, string $lockoutKey, ?User $user): void
    {
        if ($user) {
            $user->increment('failed_login_count');

            if ($user->failed_login_count >= User::LOCKOUT_THRESHOLD) {
                $user->forceFill([
                    'locked_until' => now()->addHours(User::LOCKOUT_DURATION_HOURS),
                ])->save();

                AuditLog::record('user.locked', $user, ['reason' => 'failed_login_threshold']);
            }
        }

        RateLimiter::hit($burstKey, 60);
        RateLimiter::hit($lockoutKey, 86400);
    }

    public function clear(string $burstKey, string $lockoutKey, User $user): void
    {
        $user->forceFill(['failed_login_count' => 0, 'locked_until' => null])->save();

        RateLimiter::clear($burstKey);
        RateLimiter::clear($lockoutKey);
    }

    /**
     * Per-tenant prefix so the same email in two tenants throttles
     * independently (emails are unique per tenant, not globally).
     */
    private function tenantSegment(): string
    {
        return ($this->context->tenantId() ?? 'central').':';
    }
}
