<?php

namespace app\service\admin;

use app\http\HttpAuth;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Hashids\Hashids;
use stdClass;
use app\entity\{SysLoginRecord, SysUser};
use UnexpectedValueException;
use app\enum\{UserLoginEnum, UserStatusEnum, UserTypeEnum};
use app\http\HttpClient;
use app\Request;
use app\validate\auth\LoginValidate;
use Exception;
use think\db\exception\{DataNotFoundException, DbException, ModelNotFoundException};
use think\exception\ValidateException;

class LoginService
{
    /**
     * 账号登录
     * @param Request $request
     * @param HttpClient $client
     * @param string $username
     * @param string $password
     * @param string $code
     * @param string $remember
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function login(Request $request, HttpClient $client, string $username, string $password, string $code, string $remember): array
    {
        $clientName = $client->name;
        validate(LoginValidate::class)
            ->scene($clientName . 'Login')
            ->check(compact('username', 'password', 'remember', 'code'));
        $user = SysUser::where(['username' => $username])->find();
        if (empty($user)) {
            $this->saveLoginRecord($request, $username, UserLoginEnum::USERNAME_ERROR, "用户名错误");
            throw new ValidateException("用户名或密码错误");
        }
        if (!password_verify($password, $user->password)) {
            $this->saveLoginRecord($request, $username, UserLoginEnum::PASSWORD_ERROR, "密码错误");
            throw new ValidateException("用户名或密码错误");
        }
        if ($user->status == UserStatusEnum::Disable->value) {
            $this->saveLoginRecord($request, $username, UserLoginEnum::STATUS_DISABLED, "账号被禁用");
            throw new ValidateException("账户不可用");
        }
        try {
            $access_token = $user->getAccessToken();
            $user = $user->append(['authorities', 'roles'])->toArray();
            $this->saveLoginRecord($request, $username, UserLoginEnum::LOGIN_SUCCESS, "登录成功");
        } catch (Exception $e) {
            $this->saveLoginRecord($request, $username, UserLoginEnum::LOGIN_EXCEPTION, $e->getMessage());
            throw $e;
        }
        return compact('user', 'access_token');
    }

    /**
     * 保存登录记录
     * @param Request $request
     * @param string $username
     * @param UserLoginEnum $loginType
     * @param string $comments
     * @return void
     */
    private function saveLoginRecord(Request $request, string $username, UserLoginEnum $loginType, string $comments): void
    {
        $sysLoginRecord = new SysLoginRecord();
        $sysLoginRecord->save([
            'username' => $username,
            'os' => $request->getOs(),
            'device' => $request->getDevice(),
            'browser' => $request->getBrowser(),
            'ip' => $request->ip(),
            'comments' => $comments,
            'login_type' => $loginType->value,
        ]);
    }


    public function checkUserAccessToken(string $token): HttpAuth
    {
        if (empty($token)) {
            throw new ValidateException('凭证错误');
        }
        try {
            $jwtKey = (string)config('admin.jwt_key', '');
            $headers = new stdClass();
            $data = JWT::decode($token, new Key($jwtKey, 'HS256'), $headers);
        } catch (UnexpectedValueException $e) {
            if($e->getMessage() == "Expired token") {
                throw new ValidateException('凭证过期');
            }
            throw new ValidateException('校验失败' . $e->getMessage());
        }
        if ($data->exp <= (time() - 180)) {
            // 还要三分钟就要过期
            throw new ValidateException('凭证过期');
        }
        $hashids = new Hashids($jwtKey, 8);

        $user_id = $hashids->decode($data->uid)[0] ?? 0;
        if (empty($user_id)) {
            throw new ValidateException('凭证错误');
        }
        return new HttpAuth($user_id, UserTypeEnum::USER);
    }

    public function getVisitor(\think\Request $request): HttpAuth
    {
        return new HttpAuth(-1, UserTypeEnum::VISITOR);
    }
}