<?php

namespace app\common\arw\adjfut\src\WeChat;

use app\common\arw\adjfut\src\Curl;
use app\common\arw\adjfut\src\WeChat\XcxCommon;
use think\helper\Arr;

class Xcx extends XcxCommon
{
    /**
     * 当前实例
     *
     * @var $this
     */
    private static $instance = null;
    /**
     * 初始化
     *
     * @date 2022-05-18
     * @example
     * @author arw
     * @since 1.0.0
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * 实例
     *
     * @return self
     * @date 2022-05-18
     * @example
     * @author arw
     * @since 1.0.0
     */
    public static function instance(): self
    {
        if (!self::$instance) {
            self::$instance = new self;
        }
        return self::$instance;
    }

    /**
     * 获取AccessToken
     *
     * @param boolean $refresh 是否强制刷新
     * @return void
     * @date 2020-05-29
     * @example
     * @author arw
     * @since 1.0.0
     */
    public function GetAccessToken($refresh = false)
    {
        return $this->_AccessToken($refresh);
    }

    /**
     * 获取微信小程序用户openid
     * 
     * @param string $code 微信小程序传过来的code
     * @return array|bool $res
     * @return string $res[session_key]
     * @return string $res[expores_in]
     * @return string $res[openid]
     * @return string $res[unionid]
     */
    public function GetOpenId(string $code)
    {
        try {
            $curl = Curl::instance();
            $url = 'https://api.weixin.qq.com/sns/jscode2session';
            $param = [];
            $param['appid'] = $this->appid;
            $param['secret'] = $this->secret;
            $param['js_code'] = $code;
            $param['grant_type'] = 'authorization_code';
            $res = $curl->setParams($param)->get($url);
            $openid = Arr::get($res, 'openid', false);
            if ($openid) {
                return $res;
            }
            $this->log([
                'action' =>  __FUNCTION__,
                'url' =>  $curl->getUrl(),
                'params' => $curl->getParams(),
                'content' =>  $curl->getContent(),
                'msg' =>  '获取小程序用户openid失败，微信服务器无返回openid',
            ], 'error');
            $this->error = '获取用户openid失败';
            return false;
        } catch (\Throwable $th) {
            $this->log([
                'action' =>  __FUNCTION__,
                'msg' => '客户端错误：' . $th->getMessage(),
                'line' =>  $th->getLine(),
                'file' =>  $th->getFile(),
            ], 'error');
            $this->error = $th->getMessage();
            return false;
        }
    }

    /**
     *   获取微信小程序用户unionid
     *   @param string $code 微信小程序传过来的wx.login 返回的code
     *   @param string $encryptedData 微信小程序传过来的wx.getUserInfo 返回的encryptedData
     *   @param string $iv 微信小程序传过来的wx.getUserInfo 返回的iv
     */
    public function GetUnionid(string $code, string $encryptedData, string $iv)
    {
        try {
            $result = $this->GetOpenId($code);
            if (!$result) {
                return false;
            }
            $openid = Arr::get($result, 'openid');
            $session_key = Arr::get($result, 'session_key');
            if (!$openid || !$session_key) {
                $this->error = 'code 非法';
                return false;
            }
            if (strlen($session_key) != 24) {
                $this->error = 'sessionKey 非法';
                return false;
            }
            $aesKey = base64_decode($session_key);

            if (strlen($iv) != 24) {
                $this->error = 'iv 非法';
                return false;
            }
            $aesIV = base64_decode($iv);

            $aesCipher = base64_decode($encryptedData);

            $result = openssl_decrypt($aesCipher, 'AES-128-CBC', $aesKey, 1, $aesIV);

            $data = json_decode($result, true);
            if ($data == null) {
                $this->error = 'aes 解密失败';
                return false;
            }
            if (Arr::get($data, 'watermark.appid') != $this->appid) {
                $this->error = 'appid不一致';
                return false;
            }
            return $data;
        } catch (\Throwable $th) {
            $this->log([
                'action' =>  __FUNCTION__,
                'msg' => '客户端错误：' . $th->getMessage(),
                'line' =>  $th->getLine(),
                'file' =>  $th->getFile(),
            ], 'error');
        }
    }

    /**
     *   生成微信小程序码
     *   @param string $type 接口名称 （createQRCode/get/getUnlimited）
     *   @param array $data 请求参数
     */
    public function QrCode(string $type, array $data)
    {
        return $this->_QrCode($type, $data);
    }

    private function _QrCode(string $type, array $data, bool $refresh = false, int $limit = 3)
    {
        try {
            $limit--;
            if ($limit <= 0) {
                throw new \Exception('超过调用上限', 1);
            }
            $urls = [
                'createQRCode' => 'https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode',
            ];
            $curl = Curl::instance();
            $url = $urls[$type];
            $url .= '?access_token=' . $this->GetAccessToken($refresh);
            $param = json_encode($data);
            $res = $curl->setParams($param)->post($url);

            $errcode = Arr::get($res, 'errcode', false);
            $errmsg = Arr::get($res, 'errmsg', false);
            if ($errcode === false && $errmsg === false) {
                return base64_encode($res);
            } else {
                if ($errcode === 40001) {
                    return $this->_QrCode($type, $data, true, $limit);
                }
                $this->log([
                    'action' =>  __FUNCTION__,
                    'url' =>  $curl->getUrl(),
                    'params' => $curl->getData(),
                    'content' =>  $curl->getContent(),
                    'msg' =>  '获取小程序二维码失败，微信服务器返回数据异常',
                ], 'error');
                $this->error = '获取小程序二维码失败';
                return false;
            }
        } catch (\Throwable $th) {
            $this->log([
                'action' =>  __FUNCTION__,
                'msg' => '客户端错误：' . $th->getMessage(),
                'line' =>  $th->getLine(),
                'file' =>  $th->getFile(),
            ], 'error');
            $this->error = $th->getMessage();
            return false;
        }
    }

    /**
     * 获取异常
     *
     * @return string
     * @date 2022-05-19
     * @example
     * @author arw
     * @since 1.0.0
     */
    public function getError()
    {
        return $this->error;
    }
}
