<?php
// +----------------------------------------------------------------------
// | onezan-admin 快速开发的管理平台
// +----------------------------------------------------------------------
// | 官方网址：https://www.onezan.cn
// +----------------------------------------------------------------------
// | onezan团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: 543885307@qq.com
// +----------------------------------------------------------------------
namespace app\service\api\user;

use app\event\user\UserRegistered;
use app\repository\user\UserRepository;
use app\service\AuthService as JwtAuthService;
use app\service\ConfigService;
use app\service\domain\system\CoreConfigService;
use app\service\domain\user\CoreUserService;
use app\service\SmsService;
use app\service\IdService;
use app\service\StorageService;
use app\service\api\BaseApiService;
use app\service\domain\plugins\CorePluginsAccessService;
use think\Request;
use think\facade\Event;
use think\facade\Cache;


/**
 * Auth Service
 * @package app\service\api\user
 */
class AuthService extends BaseApiService
{

    /**
     * Register a new user account
     * @param Request $request
     * @return array
     */
    public function register(Request $request): array
    {
        $username = trim((string)$request->post('username', ''));
        $password = (string)$request->post('password', '');
        $phone = trim((string)$request->post('phone', ''));
        $email = trim((string)$request->post('email', ''));

        if (ConfigService::getRegisterEnabled() !== 1) {
            return ['code' => 1, 'msg' => ''];
        }
        if ($username === '' || $password === '') {
            return ['code' => 1, 'msg' => ''];
        }
        if (strlen($password) < 6) {
            return ['code' => 1, 'msg' => ''];
        }
        if ($phone !== '' && !preg_match('/^1[3-9]\d{9}$/', $phone)) {
            return ['code' => 1, 'msg' => ''];
        }
        if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return ['code' => 1, 'msg' => ''];
        }
        if ($this->userRepository()->findByUsername($username)) {
            return ['code' => 1, 'msg' => ''];
        }

        $user = $this->userRepository()->create([
            'username' => $username,
            'phone' => $phone,
            'email' => $email,
            'invite_code' => IdService::generateUniqueInviteCode(),
            'password' => password_hash($password, PASSWORD_DEFAULT),
            'status' => 1,
        ]);

        Event::trigger(new UserRegistered(
            (int)$user->id,
            (string)$user->username,
            (string)($user->phone ?? ''),
            (string)($user->email ?? '')
        ));

        $inviteCode = trim((string)$request->post('invite_code', ''));
        if ($inviteCode !== '') {
            $this->coreUserService()->bindByInviteCode((int)$user->id, $inviteCode);
        }

        return ['code' => 0, 'msg' => ''];
    }


    /**
     * Authenticate user login with username and password
     * @param Request $request
     * @return array
     */
    public function login(Request $request): array
    {
        $username = trim((string)$request->post('username', ''));
        $password = (string)$request->post('password', '');
        if ($username === '' || $password === '') {
            return ['code' => 1, 'msg' => ''];
        }

        $user = $this->userRepository()->findByUsername($username);
        $failCacheKey = 'login_fail:' . md5($username);


        if (!$user || !password_verify($password, (string)$user->password)) {
            return $this->handleLoginFail($username, $failCacheKey);
        }

        if ((int)($user->status ?? 0) !== 1) {
            return ['code' => 1, 'msg' => ''];
        }


        Cache::delete($failCacheKey);

        return [
            'code' => 0,
            'msg' => '',
            'data' => [
                'token' => JwtAuthService::createToken((int)$user->id),
                'user' => [
                    'id' => (int)$user->id,
                    'username' => (string)$user->username,
                    'nickname' => (string)($user->nickname ?? ''),
                    'avatar' => StorageService::toFullAssetUrl($request, (string)($user->avatar ?? '')),
                    'verify_status' => (int)($user->verify_status ?? 0),
                    'level_id' => (int)($user->level_id ?? 0),
                    'vip_snapshot' => $this->pluginsAccess()->snapshot((int)$user->id),
                ],
            ],
        ];
    }


    /**
     * 处理登录失败逻辑（记录失败次数、触发账号锁定）
     * @param string $username 用户名
     * @param string $cacheKey 失败计数缓存键
     * @return array
     */
    private function handleLoginFail(string $username, string $cacheKey): array
    {
        $securityConfig = $this->securityConfig();

        if (empty($securityConfig['login_fail_enabled'])) {
            return ['code' => 1, 'msg' => ''];
        }

        $maxAttempts = (int)$securityConfig['login_fail_max'];
        $lockMinutes = (int)$securityConfig['login_fail_lock'];
        $failData = Cache::get($cacheKey);

        if (is_array($failData)) {
            $count = (int)($failData['count'] ?? 0);
            $lockedUntil = (int)($failData['locked_until'] ?? 0);
        } else {
            $count = 0;
            $lockedUntil = 0;
        }


        if ($lockedUntil > 0 && time() < $lockedUntil) {
            $remain = $lockedUntil - time();
            return ['code' => 1, 'msg' => "{$remain}"];
        }

        $count++;

        if ($count >= $maxAttempts) {
            Cache::set($cacheKey, [
                'count' => $count,
                'locked_until' => time() + $lockMinutes * 60,
            ], $lockMinutes * 60 * 2);
            return ['code' => 1, 'msg' => "{$lockMinutes}"];
        }

        Cache::set($cacheKey, [
            'count' => $count,
            'locked_until' => 0,
        ], $lockMinutes * 60 * 2);

        $remaining = $maxAttempts - $count;
        return ['code' => 1, 'msg' => "{$remaining}"];
    }


    /**
     * Send SMS verification code for login
     * @param Request $request
     * @return array
     */
    public function sendLoginCode(Request $request): array
    {
        $isDebug = app()->isDebug();

        if (!SmsService::isEnabled() && !$isDebug) {
            return ['code' => 1, 'msg' => ''];
        }

        $phone = trim((string)$request->post('phone', ''));
        if ($phone === '' || !preg_match('/^1[3-9]\d{9}$/', $phone)) {
            return ['code' => 1, 'msg' => ''];
        }

        $securityConfig = $this->securityConfig();
        $phoneHash = md5($phone);
        $ip = (string)$request->ip();
        $today = date('Ymd');


        if (!empty($securityConfig['sms_ip_rate_enabled'])) {
            $ipCacheKey = 'sms_ip_rate:' . md5($ip);
            $ipCount = (int)Cache::get($ipCacheKey, 0);
            $ipMax = (int)$securityConfig['sms_ip_rate_max'];

            if ($ipCount >= $ipMax) {
                return ['code' => 1, 'msg' => ''];
            }

            Cache::set($ipCacheKey, $ipCount + 1, 60);
        }


        if (!empty($securityConfig['sms_daily_enabled'])) {
            $dailyCacheKey = 'sms_daily:' . $phoneHash . ':' . $today;
            $dailyCount = (int)Cache::get($dailyCacheKey, 0);
            $dailyMax = (int)$securityConfig['sms_daily_max'];

            if ($dailyCount >= $dailyMax) {
                return ['code' => 1, 'msg' => ''];
            }

            Cache::set($dailyCacheKey, $dailyCount + 1, 86400);
        }


        $rateCacheKey = 'sms_login_rate:' . $phoneHash;
        if (Cache::has($rateCacheKey)) {
            $remain = (int)Cache::get($rateCacheKey, 60);
            return ['code' => 1, 'msg' => '' . max(1, $remain) . ''];
        }

        $ttl = SmsService::isEnabled() ? SmsService::getCodeTtlSeconds() : 300;

        if ($isDebug && !SmsService::isEnabled()) {
            $code = '888888';
        } else {
            $code = (string)random_int(100000, 999999);
        }

        Cache::set('sms_login_code:' . $phoneHash, $code, $ttl);
        Cache::set($rateCacheKey, 60, 60);

        if ($isDebug) {
            \think\facade\Log::info('' . $code . ' -> ' . $phone);
        }

        if ($isDebug && !SmsService::isEnabled()) {
            return ['code' => 0, 'msg' => '', 'data' => ['ttl' => $ttl, 'dev_code' => $code]];
        }

        $sendResult = SmsService::sendVerifyCode($phone, $code, 'login');
        if (!$sendResult['ok']) {
            return ['code' => 1, 'msg' => '' . ($sendResult['msg'] ?? '')];
        }

        return ['code' => 0, 'msg' => '', 'data' => ['ttl' => $ttl]];
    }


    /**
     * Authenticate user login via SMS verification code
     * @param Request $request
     * @return array
     */
    public function loginByCode(Request $request): array
    {
        $isDebug = app()->isDebug();

        if (!SmsService::isEnabled() && !$isDebug) {
            return ['code' => 1, 'msg' => ''];
        }

        $phone = trim((string)$request->post('phone', ''));
        $code = trim((string)$request->post('code', ''));

        if ($phone === '' || !preg_match('/^1[3-9]\d{9}$/', $phone)) {
            return ['code' => 1, 'msg' => ''];
        }
        if ($code === '') {
            return ['code' => 1, 'msg' => ''];
        }

        $cachedCode = \think\facade\Cache::get('sms_login_code:' . md5($phone));
        if ($cachedCode === null || $cachedCode === '') {
            return ['code' => 1, 'msg' => ''];
        }
        if ((string)$cachedCode !== $code) {
            return ['code' => 1, 'msg' => ''];
        }

        \think\facade\Cache::delete('sms_login_code:' . md5($phone));
        \think\facade\Cache::delete('sms_login_rate:' . md5($phone));

        $user = $this->userRepository()->findByPhone($phone);
        if (!$user) {
            return ['code' => 1, 'msg' => ''];
        }
        if ((int)($user->status ?? 0) !== 1) {
            return ['code' => 1, 'msg' => ''];
        }

        return [
            'code' => 0,
            'msg' => '',
            'data' => [
                'token' => \app\service\AuthService::createToken((int)$user->id),
                'user' => [
                    'id' => (int)$user->id,
                    'username' => (string)$user->username,
                    'nickname' => (string)($user->nickname ?? ''),
                    'avatar' => StorageService::toFullAssetUrl($request, (string)($user->avatar ?? '')),
                    'verify_status' => (int)($user->verify_status ?? 0),
                    'level_id' => (int)($user->level_id ?? 0),
                    'vip_snapshot' => $this->pluginsAccess()->snapshot((int)$user->id),
                ],
            ],
        ];
    }

    /**
     * 获取用户仓储实例
     * @return UserRepository
     */
    private function userRepository(): UserRepository
    {
        return $this->app->make(UserRepository::class);
    }

    /**
     * 获取用户核心服务实例
     * @return CoreUserService
     */
    private function coreUserService(): CoreUserService
    {
        return $this->app->make(CoreUserService::class);
    }

    /**
     * 获取插件访问服务实例
     * @return CorePluginsAccessService
     */
    private function pluginsAccess(): CorePluginsAccessService
    {
        return $this->app->make(CorePluginsAccessService::class);
    }


    /**
     * 获取安全配置（登录失败限制、短信频率等）
     * 安全开关关闭时所有安全特性均生效
     * @return array 安全配置数组
     */
    private function securityConfig(): array
    {
        try {
            $config = $this->app->make(CoreConfigService::class)->getSecurityConfig();


            if (empty($config['security_enabled'])) {
                return array_merge($config, [
                    'api_rate_enabled' => 0,
                    'sms_daily_enabled' => 0,
                    'sms_ip_rate_enabled' => 0,
                    'login_fail_enabled' => 0,
                ]);
            }

            return $config;
        } catch (\Throwable $e) {
            return ['security_enabled' => 0];
        }
    }
}