<?php
declare (strict_types=1);

namespace app\api\service\user;

use app\api\model\User;
use app\common\service\FileService;
use app\common\service\sms\SmsService;
use app\dict\sys\AppTypeDict;
use core\base\BaseService;
use core\util\TokenAuth;
use think\facade\Cache;

/**
 * 认证服务
 */
class UserService extends BaseService
{
    private const CODE_LOGIN_MAX_FAILS = 5;
    private const CODE_LOGIN_FAIL_TTL = 300;
    private const CODE_LOGIN_LOCK_TTL = 900;

    public function loginByCode(string $mobile, string $code, string $ip = '', string $scene = 'mobile_login')
    {
        if ($this->isCodeLoginLocked($mobile, $ip)) {
            self::setError('操作频繁，请稍后再试');
            return false;
        }

        if (!$this->verifySmsCode($mobile, $code, $scene)) {
            $this->recordCodeLoginFail($mobile, $ip);
            self::setError('验证码错误或已过期');
            return false;
        }

        $this->clearCodeLoginFail($mobile, $ip);

        $user = User::where('mobile', $mobile)->find();

        if (!$user) {
            $user = new User();
            $user->mobile = $mobile;
            $user->account = $mobile;
            $user->user_no = $this->generateUserNo();
            $user->nickname = '用户' . substr($mobile, -4);
            $user->avatar = config('project.default_image.user_avatar');
            $user->login_time = time();
            $user->login_ip = request()->ip();
            $user->save();
        } else {
            if ($user->disable == 1) {
                self::setError('账号已被禁用');
                return false;
            }

            $user->login_time = time();
            $user->login_ip = request()->ip();
            $user->save();
        }

        $tokenInfo = TokenAuth::createToken($user->id, AppTypeDict::API, [
            'user_id' => $user->id,
            'mobile' => $user->mobile,
        ], config('app.api_token_expire_time', 2592000));

        return [
            'mobile' => $user->mobile,
            'nickname' => $user->nickname,
            'avatar' => $user->avatar,
            'token' => $tokenInfo['token'],
        ];
    }

    public function loginByPassword(string $mobile, string $password)
    {
        $user = User::where('mobile', $mobile)->find();

        if (!$user || !check_password($password, $user->password)) {
            self::setError('手机号或密码错误');
            return false;
        }

        if ($user->disable == 1) {
            self::setError('账号已被禁用');
            return false;
        }

        $user->login_time = time();
        $user->login_ip = request()->ip();
        $user->save();

        $tokenInfo = TokenAuth::createToken($user->id, AppTypeDict::API, [
            'user_id' => $user->id,
            'mobile' => $user->mobile,
        ], config('app.api_token_expire_time', 2592000));

        return [
            'mobile' => $user->mobile,
            'nickname' => $user->nickname,
            'avatar' => $user->avatar,
            'token' => $tokenInfo['token'],
        ];
    }

    public function resetPassword(string $mobile, string $code, string $newPassword, string $scene = 'reset_password')
    {
        if (!$this->verifySmsCode($mobile, $code, $scene)) {
            self::setError('验证码错误或已过期');
            return false;
        }

        $user = User::where('mobile', $mobile)->find();

        if (!$user) {
            self::setError('用户不存在');
            return false;
        }

        $user->password = create_password($newPassword);
        $user->save();

        TokenAuth::clearToken($user->id, AppTypeDict::API, '');

        return true;
    }

    public function getUserInfo(int $userId)
    {
        $user = User::find($userId);

        if (!$user) {
            self::setError('用户不存在');
            return false;
        }

        return [
            'nickname' => $user->nickname,
            'avatar' => $user->avatar,
            'user_no' => $user->user_no,
            'login_time' => $user->login_time,
            'password_set' => (int)!empty($user->password) ? 1 : 2,
        ];
    }

    public function updateUserInfo(int $userId, array $params): bool
    {
        $user = User::find($userId);
        if (!$user) {
            self::setError('用户不存在');
            return false;
        }

        $avatar = $params['avatar'] ?? '';
        if ($avatar === '') {
            self::setError('请上传头像');
            return false;
        }

        $user->save([
            'avatar' => FileService::setFileUrl($avatar),
        ]);
        return true;
    }

    protected function verifySmsCode(string $mobile, string $code, string $scene): bool
    {
        $smsService = new SmsService();
        $result = $smsService->verifyCode($mobile, $code, $scene);
        if (!$result) {
            self::setError($smsService->getError());
        }
        return $result;
    }

    private function getCodeLoginFailKey(string $mobile, string $ip): string
    {
        $suffix = $ip !== '' ? $ip : 'unknown';
        return 'api_code_login_fail:' . $mobile . ':' . $suffix;
    }

    private function getCodeLoginLockKey(string $mobile, string $ip): string
    {
        $suffix = $ip !== '' ? $ip : 'unknown';
        return 'api_code_login_lock:' . $mobile . ':' . $suffix;
    }

    private function isCodeLoginLocked(string $mobile, string $ip): bool
    {
        return Cache::has($this->getCodeLoginLockKey($mobile, $ip));
    }

    private function recordCodeLoginFail(string $mobile, string $ip): void
    {
        $failKey = $this->getCodeLoginFailKey($mobile, $ip);
        $fails = (int)Cache::get($failKey, 0) + 1;
        Cache::set($failKey, $fails, self::CODE_LOGIN_FAIL_TTL);

        if ($fails >= self::CODE_LOGIN_MAX_FAILS) {
            Cache::set($this->getCodeLoginLockKey($mobile, $ip), 1, self::CODE_LOGIN_LOCK_TTL);
        }
    }

    private function clearCodeLoginFail(string $mobile, string $ip): void
    {
        Cache::delete($this->getCodeLoginFailKey($mobile, $ip));
        Cache::delete($this->getCodeLoginLockKey($mobile, $ip));
    }

    protected function generateUserNo(): string
    {
        $prefix = 'WD';
        $date = date('Ymd');

        $lastUser = User::where('user_no', 'like', $prefix . $date . '%')
            ->order('user_no', 'desc')
            ->find();

        if ($lastUser && $lastUser->user_no) {
            $lastNo = intval(substr($lastUser->user_no, -3));
            $newNo = $lastNo + 1;
        } else {
            $newNo = 1;
        }

        return $prefix . $date . str_pad((string)$newNo, 3, '0', STR_PAD_LEFT);
    }
}
