<?php

namespace App\Services\Auth;

use App\Models\IpAttempt;
use App\Models\LoginActivity;
use App\Models\Configuration;
use App\Models\User;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

/**
 * Centralizes API login lockout, suspension, and audit logging behavior.
 */
class ApiLoginSecurityService
{
    private const MAX_ATTEMPTS = 10;

    private const BLOCK_HOURS = 24;

    public function isIpBlocked(string $ip): bool
    {
        $row = IpAttempt::query()->where('ip_address', $ip)->first();
        if (!$row || !$row->blocked_until) {
            return false;
        }

        return $row->blocked_until->isFuture();
    }

    public function logIpAttempt(string $ip): void
    {
        $now = CarbonImmutable::now('UTC');
        $nowStr = $now->toDateTimeString();

        $attempt = IpAttempt::query()->where('ip_address', $ip)->first();
        if ($attempt) {
            $previouslyBlocked = $attempt->blocked_until
                ? CarbonImmutable::parse($attempt->blocked_until, 'UTC')->isFuture()
                : false;

            // If the previous block has already expired, start a fresh window
            // so we don't perma-ban an IP after its first 10 lifetime failures.
            $attempts = $previouslyBlocked
                ? ((int) $attempt->attempts) + 1
                : 1;

            $blockedUntil = $attempts >= self::MAX_ATTEMPTS
                ? $now->addHours(self::BLOCK_HOURS)->toDateTimeString()
                : null;

            $attempt->update([
                'attempts' => $attempts,
                'last_attempt_at' => $nowStr,
                'blocked_until' => $blockedUntil,
            ]);

            return;
        }

        IpAttempt::query()->create([
            'ip_address' => $ip,
            'attempts' => 1,
            'last_attempt_at' => $nowStr,
        ]);
    }

    public function handleFailedLogin(User $user, string $email, string $ip): void
    {
        $user->refresh();

        $failedAttempts = ((int) ($user->failed_attempts ?? 0)) + 1;
        $data = [
            'failed_attempts' => $failedAttempts,
            'last_failed_at' => utc_now(),
        ];

        if ($failedAttempts >= 3) {
            $data['is_suspended'] = true;
            // Password-reset delivery can be wired here when the suspension workflow is finalized.
            Log::notice('api_login: account suspended after failed attempts', ['email' => $email, 'user_id' => $user->id]);
        }

        $user->fill($data);
        $user->save();

        $this->logIpAttempt($ip);
    }

    public function resetFailedAttempts(User $user): void
    {
        $user->failed_attempts = 0;
        $user->last_failed_at = null;
        $user->save();
    }

    public function logSuccessfulLogin(User $user, Request $request): void
    {
        $schoolYear = trim((string) (Configuration::getConfig('school_year') ?? date('Y')));
        $semester = trim((string) (Configuration::getConfig('semester') ?? 'Fall'));

        LoginActivity::query()->create([
            'user_id' => $user->id,
            'email' => $user->email,
            'login_time' => utc_now(),
            'ip_address' => $request->ip(),
            'user_agent' => (string) ($request->userAgent() ?? ''),
            'school_year' => $schoolYear,
            'semester' => $semester !== '' ? $semester : 'Fall',
        ]);
    }

    /**
     * Returns roles as an object map {"RoleName": true}.
     *
     * @param  list<string>  $roleNames
     */
    public function rolesToObjectMap(array $roleNames): object
    {
        $rolesMap = [];
        foreach ($roleNames as $r) {
            $rolesMap[$r] = true;
        }

        return (object) $rolesMap;
    }

    /**
     * Top-level JSON body for API login success responses.
     *
     * @return array{status: true, token: string, access_token: string, token_type: string, expires_in: int, user: array{id: int, name: string, firstname: ?string, lastname: ?string, email: ?string, roles: object, class_section_id: ?int, class_section_name: ?string}}
     */
    public function buildLoginResponse(User $user, string $jwtToken): array
    {
        $roleNames = $user->roleNames();
        $fullName = trim(($user->firstname ?? '').' '.($user->lastname ?? ''));
        $teacherContext = $user->teacherSessionContext();

        return [
            'status' => true,
            // Keep both names. Older clients read `token`; JWT-aware clients often read
            // `access_token`. Removing either one just makes login fail in a new and
            // irritatingly avoidable way.
            'token' => $jwtToken,
            'access_token' => $jwtToken,
            'token_type' => 'bearer',
            'expires_in' => (int) config('jwt.ttl') * 60,
            'user' => [
                'id' => (int) $user->id,
                'name' => $fullName,
                'firstname' => $user->firstname,
                'lastname' => $user->lastname,
                'email' => $user->email,
                'roles' => $this->rolesToObjectMap($roleNames),
                'class_section_id' => $teacherContext['class_section_id'],
                'class_section_name' => $teacherContext['class_section_name'],
            ],
        ];
    }
}
