<?php

namespace Kylin\Auth\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Kylin\Auth\Enums\AuthErrorCode;
use Kylin\Auth\Exceptions\AuthException;
use Kylin\User\Enums\UserStatus;
use Kylin\User\Models\User;
use Kylin\User\Models\UserProfile;

/**
 * 认证服务
 *
 * 提供用户登录、注册、退出、Token 刷新等核心认证功能。
 *
 * 安全特性：
 *  - 登录频率限制（防止暴力破解，默认 5 次/分钟）
 *  - 验证码校验（图形验证码/短信验证码）
 *  - 旧密码明文自动迁移为 Bcrypt 哈希
 *  - 密码过期检查（可配置过期天数）
 *  - 账号状态校验（禁用/锁定）
 */
class AuthService
{
    /**
     * 用户登录
     *
     * 登录流程：
     *  1. 检查登录频率限制（防止暴力破解）
     *  2. 验证验证码（如提供）
     *  3. 解析用户身份（支持邮箱/手机/用户名）
     *  4. 验证密码（Bcrypt 优先，兼容旧明文密码）
     *  5. 旧明文密码自动迁移为 Bcrypt 哈希
     *  6. 检查账号状态（禁用/锁定）
     *  7. 检查密码是否过期
     *  8. 生成 Sanctum 令牌
     *  9. 更新登录信息（IP、时间）
     *
     * @param  string       $account      登录账号（邮箱/手机/用户名）
     * @param  string       $password     密码
     * @param  string|null  $captchaKey   验证码 key
     * @param  string|null  $captchaCode  验证码
     * @return array  ['user' => User, 'token' => string, 'token_type' => 'Bearer']
     *
     * @throws AuthException 登录失败时抛出
     */
    public function login(string $account, string $password, ?string $captchaKey = null, ?string $captchaCode = null): array
    {
        $this->checkRateLimit($account);

        if ($captchaKey && $captchaCode) {
            $this->verifyCaptcha($captchaKey, $captchaCode);
        }

        $user = $this->resolveUser($account);

        $passwordValid = false;
        try {
            if ($user && Hash::check($password, $user->password)) {
                $passwordValid = true;
            }
        } catch (\Throwable $e) {
            // 密码不是 Bcrypt 格式，尝试明文匹配
        }
        if (! $passwordValid && $user && $user->password === $password) {
            // 兼容旧数据：明文密码自动迁移为 Bcrypt 哈希
            $user->password = Hash::make($password);
            $user->save();
            $passwordValid = true;
        }

        if (! $user || ! $passwordValid) {
            $lockoutMinutes = (int) config('kylin-auth.login_security.lockout_minutes', 30);
            RateLimiter::hit($this->throttleKey($account), $lockoutMinutes * 60);
            throw new AuthException(AuthErrorCode::LoginFailed);
        }

        if ($user->status === UserStatus::Disabled) {
            throw new AuthException(AuthErrorCode::AccountDisabled);
        }

        if ($user->status === UserStatus::Locked) {
            throw new AuthException(AuthErrorCode::AccountLocked);
        }

        // 检查密码是否过期
        $expireDays = config('kylin-auth.password.max_age_days', 0);
        if ($expireDays > 0 && $user->password_changed_at) {
            if (now()->diffInDays($user->password_changed_at) > $expireDays) {
                throw new AuthException(AuthErrorCode::PasswordExpired);
            }
        }

        RateLimiter::clear($this->throttleKey($account));

        $token = $user->createToken('auth-token', expiresAt: now()->addMinutes(
            config('kylin-auth.token.expiration', 60)
        ));

        try {
            $user->update([
                'login_ip' => request()->ip(),
                'login_at' => now(),
            ]);
        } catch (\Throwable $e) {
            logger()->warning('更新登录信息失败', ['user_id' => $user->id, 'error' => $e->getMessage()]);
        }

        return [
            'user' => $user->load('profile'),
            'token' => $token->plainTextToken,
            'token_type' => 'Bearer',
        ];
    }

    /**
     * 用户注册
     *
     * 自动识别账号类型（邮箱/手机号/用户名），
     * 创建用户记录并生成认证令牌。
     *
     * @param  string       $account   账号（邮箱/手机/用户名）
     * @param  string       $password  密码
     * @param  string|null  $nickname  昵称（null 则使用账号名）
     * @return array  ['user' => User, 'token' => string, 'token_type' => 'Bearer']
     *
     * @throws AuthException
     */
    public function register(string $account, string $password, ?string $nickname = null): array
    {
        // 检查账号是否已存在
        $exists = User::where('name', $account)
            ->orWhere('email', $account)
            ->orWhere('phone', $account)
            ->exists();

        if ($exists) {
            throw new AuthException(AuthErrorCode::RegisterDuplicateAccount);
        }

        // 依据等保 2.0 (GB/T 22239)，密码复杂度校验
        $this->validatePasswordComplexity($password);

        try {
            $user = User::create([
                'name' => $account,
                'email' => filter_var($account, FILTER_VALIDATE_EMAIL) ? $account : null,
                'phone' => preg_match('/^1[3-9]\d{9}$/', $account) ? $account : null,
                'password' => Hash::make($password),
                'status' => UserStatus::Active,
                'password_changed_at' => now(),
            ]);

            // 创建空的用户档案
            $user->profile()->create([
                'nickname' => $nickname ?? $account,
            ]);

            // 生成 token
            $token = $user->createToken('auth-token', expiresAt: now()->addMinutes(
                config('kylin-auth.token.expiration', 60)
            ));

            return [
                'user' => $user->load('profile'),
                'token' => $token->plainTextToken,
                'token_type' => 'Bearer',
            ];
        } catch (\Throwable $e) {
            logger()->error('用户注册失败', ['account' => $account, 'error' => $e->getMessage()]);
            throw new AuthException(AuthErrorCode::RegisterFailed);
        }
    }

    /**
     * 用户退出登录
     *
     * 删除当前 Sanctum 访问令牌。
     */
    public function logout(Authenticatable $user): void
    {
        $user->currentAccessToken()->delete();
    }

    /**
     * 刷新认证令牌
     *
     * 删除当前令牌并生成新令牌，实现令牌轮换。
     */
    public function refreshToken(Authenticatable $user): string
    {
        $user->currentAccessToken()->delete();
        $token = $user->createToken('auth-token', expiresAt: now()->addMinutes(
            config('kylin-auth.token.expiration', 60)
        ));
        return $token->plainTextToken;
    }

    /**
     * 获取当前用户信息
     *
     * 加载用户档案（profile）关联数据。
     */
    public function me(Authenticatable $user): Authenticatable
    {
        return $user->load('profile');
    }

    /**
     * 生成验证码
     *
     * 生成 6 位随机数字验证码，存储到缓存中。
     * 返回 captcha_key 用于后续验证。
     *
     * @return array  ['captcha_key' => string, 'captcha_code' => string]
     */
    public function generateCaptcha(): array
    {
        $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
        $key = 'captcha:' . uniqid();
        Cache::put($key, $code, config('kylin-auth.captcha.ttl', 300));
        return ['captcha_key' => $key, 'captcha_code' => $code];
    }

    /**
     * 解析用户身份
     *
     * 支持按邮箱、手机号、用户名查找用户。
     */
    private function resolveUser(string $account): ?User
    {
        return User::where('email', $account)
            ->orWhere('phone', $account)
            ->orWhere('name', $account)
            ->first();
    }

    /**
     * 验证验证码
     *
     * 从缓存中获取验证码并比对，验证后立即删除缓存（一次性使用）。
     *
     * @throws AuthException 验证码无效时抛出
     */
    private function verifyCaptcha(string $key, string $code): void
    {
        $cached = Cache::get($key);
        Cache::forget($key);

        if (! $cached || $cached !== $code) {
            throw new AuthException(AuthErrorCode::CaptchaInvalid);
        }
    }

    /**
     * 检查登录频率限制
     *
     * 基于账号和 IP 组合进行限流，防止暴力破解。
     * 依据等保 2.0 (GB/T 22239) 8.1.4.4，登录失败处理。
     *
     * @throws AuthException 超过限制时抛出
     */
    private function checkRateLimit(string $account): void
    {
        $key = $this->throttleKey($account);
        $maxAttempts = (int) config('kylin-auth.login_security.max_attempts', 5);
        $lockoutMinutes = (int) config('kylin-auth.login_security.lockout_minutes', 30);

        if (RateLimiter::tooManyAttempts($key, $maxAttempts)) {
            $seconds = RateLimiter::availableIn($key);
            throw new AuthException(
                AuthErrorCode::TooManyAttempts,
                message: "登录失败次数过多，请 {$lockoutMinutes} 分钟后再试"
            );
        }
    }

    /**
     * 生成限流键名
     *
     * 格式：login:{account}:{ip}
     */
    private function throttleKey(string $account): string
    {
        return 'login:' . $account . ':' . request()->ip();
    }

    /**
     * 验证密码复杂度
     *
     * 依据等保 2.0 (GB/T 22239)，密码需满足复杂度要求。
     *
     * @throws AuthException 密码不满足复杂度要求时抛出
     */
    private function validatePasswordComplexity(string $password): void
    {
        $minLength = (int) config('kylin-auth.password.min_length', 8);
        $requireUpper = config('kylin-auth.password.require_uppercase', true);
        $requireLower = config('kylin-auth.password.require_lowercase', true);
        $requireNumber = config('kylin-auth.password.require_number', true);
        $requireSpecial = config('kylin-auth.password.require_special', true);

        $errors = [];

        if (mb_strlen($password) < $minLength) {
            $errors[] = "密码长度至少 {$minLength} 位";
        }
        if ($requireUpper && ! preg_match('/[A-Z]/', $password)) {
            $errors[] = '密码需包含大写字母';
        }
        if ($requireLower && ! preg_match('/[a-z]/', $password)) {
            $errors[] = '密码需包含小写字母';
        }
        if ($requireNumber && ! preg_match('/[0-9]/', $password)) {
            $errors[] = '密码需包含数字';
        }
        if ($requireSpecial && ! preg_match('/[^a-zA-Z0-9]/', $password)) {
            $errors[] = '密码需包含特殊字符';
        }

        if (! empty($errors)) {
            throw new AuthException(AuthErrorCode::RegisterFailed, message: implode('；', $errors));
        }
    }
}
