<?php

namespace App\libs\wechat\auth\Gateways;

use App\libs\cache\redis\Redis;
use App\libs\helpers\Curl;
use Illuminate\Support\Facades\DB;

/**
 * @desc    微信小程序类
 */
class Mini extends Base
{
    private $appId;
    private $secret;

    public function __construct()
    {
        parent::__construct();

        $this->appId  = $this->config['appid'];
        $this->secret = $this->config['secret'];
    }

    /**
     * @see    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
     *
     * @desc    使用授权code换取openid
     */
    public function code2Openid(string $code): array
    {
        $url = 'https://api.weixin.qq.com/sns/jscode2session';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'appid'      => $this->appId,
            'secret'     => $this->secret,
            'js_code'    => $code,
            'grant_type' => 'authorization_code',
        ]));

        return $res ? json_decode($res, true) : [];
    }

    /**
     * @see    https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
     *
     * @desc    解密小程序信息
     *
     * @param string $sessionKey    session_key
     * @param string $encryptedData 加密字符
     * @param string $iv            偏移量
     */
    public function decrypt($sessionKey, $encryptedData, $iv): array
    {
        if (24 !== \strlen($iv)) {
            // 偏移量错误
            return ['code' => 41002, 'data' => '', 'msg' => 'iv解密失败'];
        }
        if (24 !== \strlen($sessionKey)) {
            // sessionkey 错误
            return ['code' => 41001, 'data' => '', 'msg' => 'sessionKey解密失败'];
        }

        $data = openssl_decrypt(base64_decode($encryptedData, true), 'AES-128-CBC', base64_decode($sessionKey, true), OPENSSL_RAW_DATA, base64_decode($iv, true));
        if (!$data) {
            return ['code' => 41001, 'data' => '', 'msg' => '解密失败'];
        }
        $userInfo = json_decode($data, true);
        if ($userInfo['watermark']['appid'] !== $this->appId) {
            return ['code' => 41003, 'data' => '', 'msg' => 'appid不匹配'];
        }

        $userInfo['nickName'] = !empty($userInfo['nickName']) ? $userInfo['nickName'] : '';

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

    /**
     * @see    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.createQRCode.html
     *
     * @desc    获取小程序码(限量)
     *
     * @param string $path  页面路径
     * @param int    $width 二维码宽度
     */
    public function qrcode(string $path, $width = 430)
    {
        $params = ['path' => $path, 'width' => $width];
        $url    = 'https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=' . $this->accessToken();

        return Curl::requestCurl($url, 'POST', [], json_encode($params));
    }

    /**
     * @see    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.getUnlimited.html
     *
     * @desc    获取小程序码(不限量)
     *
     * @param string $scene 场景值
     * @param array  $extra 额外参数,详见
     *
     * @return bool|mixed|string
     */
    public function qrcodeUnlimited(string $scene, $extra = [])
    {
        $params = array_merge($extra, ['scene' => $scene]);
        $url    = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' . $this->accessToken();

        return Curl::requestCurl($url, 'POST', [], json_encode($params));
    }

    /**
     * @see    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.generate.html
     *
     * @desc    生成短链接
     *
     * @return bool|string
     */
    public function urlLink(array $params)
    {
        $url = 'https://api.weixin.qq.com/wxa/generate_urllink?access_token=' . $this->accessToken();

        return Curl::requestCurl($url, 'POST', [], json_encode($params));
    }

    /**
     * @see    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
     *
     * @desc    发送模板消息
     *
     * @param string $openid   小程序openid
     * @param string $template 模板ID
     * @param array  $data     小程序数据
     * @param string $page     跳转页面
     * @param string $state    小程序状态
     *
     * @return bool|mixed
     */
    public function sendMiniMsg(string $openid, string $template, array $data, string $page = null, string $state = 'normal'): array
    {
        $params = [
            'touser'            => $openid,
            'template_id'       => $template,
            'miniprogram_state' => $state,
            'data'              => $data,
        ];
        if ($page) {
            $params['page'] = $page;
        }
        $url = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=' . $this->accessToken();
        $res = Curl::requestCurl($url, 'POST', [], json_encode($params));

        return $res ? json_decode($res, true) : [];
    }

    /**
     * @see    https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html
     *
     * @desc    获取全局唯一接口调用凭据
     *
     * @param int $repeatCnt
     *
     * @return string
     */
    public function accessToken(&$repeatCnt = 0)
    {
        $drive = $this->config['token_drive'] ?? 'redis';
        $key   = 'mini_' . $this->appId . '_accessToken';

        switch ($drive) {
            case 'redis':
                $redis = Redis::instance();
                $redis->select(15);

                $token = $redis->get($key);

                break;

            case 'db':
            default:
                $token = Db::table('configs')->where('c_key', $key)->where('c_expired', '>', time())->value('c_value');

                break;
        }
        if ($token) {
            return $token;
        }
        $url = 'https://api.weixin.qq.com/cgi-bin/token';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'grant_type' => 'client_credential',
            'appid'      => $this->appId,
            'secret'     => $this->secret,
        ]));

        $data = json_decode($res, true);
        if (isset($data['access_token'])) {
            $token = $data['access_token'];

            switch ($drive) {
                case 'redis':
                    $redis->setex($key, 7000, $token);

                    break;

                case 'db':
                default:
                    $isWin = Db::table('configs')->where('c_key', $key)->value('c_value');
                    if ($isWin) {
                        Db::table('configs')
                            ->where('c_key', $key)
                            ->update([
                                'c_value'   => $token,
                                'c_expired' => time() + 7000,
                                'c_updated' => date('Y-m-d H:i:s'),
                            ])
                        ;
                    } else {
                        Db::table('configs')
                            ->insert([
                                'c_key'     => $key,
                                'c_value'   => $token,
                                'c_expired' => time() + 7000,
                                'c_updated' => date('Y-m-d H:i:s'),
                            ])
                        ;
                    }

                    break;
            }

            return $token;
        }
        if ($repeatCnt <= 3) {
            ++$repeatCnt;

            return $this->accessToken($repeatCnt);
        }

        return $data['errcode'];
    }
}
