<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\InviteCode;
use App\Models\OperationLog;
use App\Models\User;
use App\Support\ApiResponse;
use hg\apidoc\annotation as Apidoc;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Tymon\JWTAuth\Facades\JWTAuth;

#[Apidoc\Group('认证')]
#[Apidoc\Title('认证接口')]
final class AuthController extends Controller
{
    #[Apidoc\Title('邀请码注册')]
    #[Apidoc\Url('/api/auth/register')]
    #[Apidoc\Method('POST')]
    public function register(Request $request): JsonResponse
    {
        $data = $request->validate([
            'name' => ['required', 'string', 'max:50'],
            'email' => ['required', 'email', 'max:120', 'unique:users,email'],
            'password' => ['required', 'string', 'min:6', 'confirmed'],
            'invite_code' => ['required', 'string'],
        ]);

        $invite = InviteCode::query()->where('code', $data['invite_code'])->lockForUpdate()->first();
        if (! $invite || ! $invite->available()) {
            throw ValidationException::withMessages(['invite_code' => '邀请码无效']);
        }

        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'role' => $invite->role,
            'is_active' => true,
            'password' => Hash::make($data['password']),
        ]);

        $invite->increment('used_count');

        return ApiResponse::success($this->tokenPayload($user), '注册成功');
    }

    #[Apidoc\Title('验证码')]
    #[Apidoc\Url('/api/auth/captcha')]
    #[Apidoc\Method('GET')]
    public function captcha(Request $request): JsonResponse
    {
        $code = (string) random_int(1000, 9999);
        $request->session()->put('captcha', $code);

        return ApiResponse::success([
            'captcha' => $code,
            'expires_in' => 300,
        ], '验证码已生成');
    }

    #[Apidoc\Title('登录')]
    #[Apidoc\Url('/api/auth/login')]
    #[Apidoc\Method('POST')]
    public function login(Request $request): JsonResponse
    {
        $data = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required', 'string'],
            'captcha' => ['nullable', 'string'],
        ]);

        $key = 'login:'.$request->ip().':'.$data['email'];
        $user = User::query()->where('email', $data['email'])->first();

        if (RateLimiter::tooManyAttempts($key, 5) || ($user?->failed_login_count ?? 0) >= 5) {
            $captcha = (string) ($data['captcha'] ?? '');
            $expectedCaptcha = (string) session('captcha', '');

            if ($captcha === '' || $expectedCaptcha === '' || $captcha !== $expectedCaptcha) {
                return ApiResponse::error('请输入验证码', 429, 429, ['captcha_required' => true]);
            }
        }

        if (! $user || ! Hash::check($data['password'], $user->password)) {
            RateLimiter::hit($key, 300);
            $user?->update([
                'failed_login_count' => $user->failed_login_count + 1,
                'last_failed_login_at' => now(),
            ]);

            return ApiResponse::error('账号或密码错误', 422, 422, [
                'captcha_required' => RateLimiter::attempts($key) >= 5,
            ]);
        }

        if (! $user->is_active) {
            return ApiResponse::error('账号已被禁用', 403, 403);
        }

        RateLimiter::clear($key);
        $user->update(['failed_login_count' => 0, 'last_login_at' => now()]);

        OperationLog::create([
            'user_id' => $user->id,
            'action' => 'auth.login',
            'ip' => $request->ip(),
        ]);

        return ApiResponse::success($this->tokenPayload($user), '登录成功');
    }

    #[Apidoc\Title('刷新Token')]
    #[Apidoc\Url('/api/auth/refresh')]
    #[Apidoc\Method('POST')]
    public function refresh(): JsonResponse
    {
        return ApiResponse::success([
            'token' => JWTAuth::refresh(JWTAuth::getToken()),
            'token_type' => 'bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60,
        ]);
    }

    #[Apidoc\Title('当前用户')]
    #[Apidoc\Url('/api/auth/me')]
    #[Apidoc\Method('GET')]
    #[Apidoc\RouteMiddleware(['jwt.auth'])]
    public function me(Request $request): JsonResponse
    {
        return ApiResponse::success($request->user()->loadMissing('permissions'));
    }

    #[Apidoc\Title('退出登录')]
    #[Apidoc\Url('/api/auth/logout')]
    #[Apidoc\Method('POST')]
    #[Apidoc\RouteMiddleware(['jwt.auth'])]
    public function logout(): JsonResponse
    {
        JWTAuth::invalidate(JWTAuth::getToken());

        return ApiResponse::success(null, '已退出');
    }

    #[Apidoc\Title('发送找回密码邮件')]
    #[Apidoc\Url('/api/auth/forgot-password')]
    #[Apidoc\Method('POST')]
    public function forgotPassword(Request $request): JsonResponse
    {
        $data = $request->validate(['email' => ['required', 'email']]);
        $user = User::query()->where('email', $data['email'])->first();

        if (! $user) {
            return ApiResponse::success(null, '如果邮箱存在，系统会发送重置邮件');
        }

        $token = Password::broker()->createToken($user);

        if (config('mail.default') !== 'smtp' || ! config('mail.mailers.smtp.host')) {
            OperationLog::create([
                'user_id' => $user->id,
                'action' => 'auth.password_reset_token_created',
                'payload' => ['token' => $token],
            ]);

            return ApiResponse::success(['token' => $token], '邮件未配置，已返回重置 token');
        }

        Mail::raw("QuickQuiz 密码重置 Token：{$token}", fn ($message) => $message->to($user->email)->subject('QuickQuiz 密码重置'));

        return ApiResponse::success(null, '重置邮件已发送');
    }

    #[Apidoc\Title('重置密码')]
    #[Apidoc\Url('/api/auth/reset-password')]
    #[Apidoc\Method('POST')]
    public function resetPassword(Request $request): JsonResponse
    {
        $data = $request->validate([
            'email' => ['required', 'email'],
            'token' => ['required', 'string'],
            'password' => ['required', 'string', 'min:6', 'confirmed'],
        ]);

        $status = Password::broker()->reset($data, function (User $user, string $password): void {
            $user->forceFill([
                'password' => Hash::make($password),
                'remember_token' => Str::random(60),
            ])->save();
        });

        if ($status !== Password::PASSWORD_RESET) {
            return ApiResponse::error('重置失败', 422, 422, ['status' => $status]);
        }

        return ApiResponse::success(null, '密码已重置');
    }

    private function tokenPayload(User $user): array
    {
        return [
            'token' => JWTAuth::fromUser($user),
            'token_type' => 'bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60,
            'user' => $user,
        ];
    }
}
