<?php

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

use app\common\arw\adjfut\src\Curl;
use app\common\arw\adjfut\src\WeChat\GzhCommon;
use think\facade\Config;
use think\facade\Request;
use think\helper\Arr;
use think\Response;

class Gzh extends GzhCommon
{
	/**
	 * 当前实例
	 *
	 * @var $this
	 */
	private static $instance = null;

	/**
	 * 推送缓存 防止重复推送同样的数据
	 *
	 * @var array
	 * @date 2021-10-11
	 * @example
	 * @author arw
	 * @since 1.0.0
	 */
	private static $TemplateMessageInterfaceCache = [];
	/**
	 * 初始化
	 *
	 * @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);
	}

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

	/**
	 *   网页授权/获取用户基本信息
	 *    https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
	 *    @param array $config 配置项 [
	 *       "scope" => "snsapi_base",   应用授权作用域，
	 *       snsapi_base （不弹出授权页面，直接跳转，只能获取用户openid），
	 *       snsapi_userinfo （弹出授权页面，可通过openid拿到昵称、性别、所在地。并且， 即使在未关注的情况下，只要用户授权，也能获取其信息 ）
	 *       "state" => "",  重定向后会带上state参数，开发者可以填写a-zA-Z0-9的参数值，最多128字节
	 *       "lang" => "zh_CN",   当scope等于snsapi_userinfo才生效
	 *       返回国家地区语言版本，zh_CN 简体，zh_TW 繁体，en 英语
	 *    ]
	 *    @param callable $success 回调用户授权成功调用函数     ($userinfo,$state)
	 *    @param callable $error 执行错误回调   ($msg)
	 *    @return null
	 */
	public function authorize(array $config, callable $success, callable $error)
	{
		try {
			$config["scope"] = Arr::get($config, "scope", "snsapi_base");
			$config["state"] = Arr::get($config, "state", "");
			$config["lang"] = Arr::get($config, "lang", "zh_CN");

			$config["redirect_uri"] = Arr::get($config, "redirect_uri", Request::url(true));

			$code = Arr::get($_GET, "code");

			if (!$code) {
				$url = "https://open.weixin.qq.com/connect/oauth2/authorize";
				$url .= "?" . http_build_query([
					"appid" => $this->appid,
					"redirect_uri" => $config["redirect_uri"],
					"response_type" => "code",
					"scope" => $config["scope"],
					"state" => $config["state"],
				]);
				$url .= "#wechat_redirect";
				return Response::create($url, 'redirect', 302);
			}
			$curl = Curl::instance();
			$param = [];
			$param["appid"] = $this->appid;
			$param["secret"] = $this->secret;
			$param["code"] = $code;
			$param["grant_type"] = "authorization_code";
			$url = "https://api.weixin.qq.com/sns/oauth2/access_token";
			$access_token = $curl->setParams($param)->get($url);

			$judge = (Arr::get($access_token, "access_token") && Arr::get($access_token, "openid"));
			if (!$judge || !$access_token) {
				$this->log([
					'action' =>  __FUNCTION__,
					'url' =>  $curl->getUrl(),
					'params' => $curl->getParams(),
					'content' =>  $curl->getContent(),
					'msg' =>  '获取access_token失败',
				], 'error');
				return $error("获取access_token失败");
			}
			if ($config["scope"] === "snsapi_userinfo") {
				$curl = Curl::instance();
				$url = "https://api.weixin.qq.com/sns/userinfo";
				$param = [];
				$param["access_token"] = Arr::get($access_token, "access_token");
				$param["openid"] = Arr::get($access_token, "openid");
				$param["lang"] = $config["lang"];
				$user_info = $curl->setParams($param)->get($url);
				$errcode = Arr::get($user_info, "errcode", false);
				if ($errcode !== false) {
					$this->log([
						'action' =>  __FUNCTION__,
						'url' =>  $curl->getUrl(),
						'params' => $curl->getParams(),
						'result' =>  $curl->getContent(),
						'msg' =>  '获取用户信息失败',
					], 'error');
					return $error("获取微信用户信息失败");
				}
				$access_token = array_merge($access_token, $user_info);
			}
			return $success($access_token, $_GET["state"]);
		} catch (\Throwable $th) {
			$this->log([
				'action' =>  __FUNCTION__,
				'msg' => '客户端错误：' . $th->getMessage(),
				'line' =>  $th->getLine(),
				'file' =>  $th->getFile(),
			], 'error');
			return $error($th->getMessage());
		}
	}

	/**
	 * 微信模板消息推送
	 * 微信返回errcode等于40001会更新缓存AccessToken
	 * 并重新推送一次
	 * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html#5
	 * @param string $touser 推送给谁
	 * @param string $template_id 模板名称
	 * @param array $data 推送数据
	 *      [
	 *         "touser" => "openid",
	 *         "template_id" => "template_id",
	 *         "url" => "url",
	 *         "miniprogram" => [
	 *              "appid" => "appid",
	 *              "pagepath" => "pagepath",
	 *          ],
	 *         "data" => [
	 *              "first" => [
	 *                  "value" => "value",
	 *                  "color" => "color",
	 *              ],
	 *              "keyword1" => [
	 *                  "value" => "value",
	 *                  "color" => "color",
	 *              ],
	 *              "keyword2" => [
	 *                  "value" => "value",
	 *                  "color" => "color",
	 *              ],
	 *              "keyword3" => [
	 *                  "value" => "value",
	 *                  "color" => "color",
	 *              ],
	 *              "remark" => [
	 *                  "value" => "value",
	 *                  "color" => "color",
	 *              ],
	 *          ],
	 *      ]
	 * @param boolean $refresh 是否刷新【 access_token 】
	 * @param integer $limit 调用上限（允许自动重试次数）
	 * @return bool
	 * @date 2020-05-26
	 * @example
	 * @author arw
	 * @since 1.1.1
	 */
	public function TemplateMessageInterface($touser = null, $template_id = null, array $data, $refresh = false, $limit = 3)
	{
		$this->log([
			'action' =>  __FUNCTION__,
			'touser' => $touser,
			'template_id' =>  $template_id,
			'data' =>  $data,
		], 'log');
		try {
			$template = $this->template;
			if (!$touser) {
				if (isset($data["touser"])) {
					$touser = $data["touser"];
				}
			}
			if (!$template_id) {
				if (isset($data["template_id"])) {
					$template_id = $data["template_id"];
				}
			}
			if (empty($touser)) {
				throw new \Exception("data.touser不能为空", 1);
			}
			if (empty($template_id)) {
				throw new \Exception("data.template_id不能为空", 1);
			} else {
				if (isset($template[$template_id])) {
					$template_id = $template[$template_id];
				}
			}
			$data["touser"] = $touser;
			$data["template_id"] = $template_id;
			$limit--;
			if ($limit <= 0) {
				throw new \Exception("超过调用上限", 1);
			}
			$access_token = $this->_AccessToken($refresh);
			if (!$access_token) {
				return false;
			}
			$appid = Arr::get($data, "miniprogram.appid");
			if ($appid == 'APPID') {
				Arr::set($data, 'miniprogram.appid', Config::get('xcx.appid'));
			}
			$param = json_encode($data);
			if (!isset(self::$TemplateMessageInterfaceCache[$param])) {
				self::$TemplateMessageInterfaceCache[$param] = [];
			}
			if (in_array($touser, self::$TemplateMessageInterfaceCache[$param])) {
				throw new \Exception("重复推送", 1);
			}
			$url = "https://api.weixin.qq.com/cgi-bin/message/template/send";
			$url .= "?access_token=" . $access_token;
			$curl = Curl::instance();
			$send = $curl->setData($param)->post($url);

			$errcode = Arr::get($send, "errcode", false);
			if ($errcode === 0) {
				self::$TemplateMessageInterfaceCache[$param][] = $touser;
				return true;
			}
			if ($errcode === 40001) {
				return $this->TemplateMessageInterface($touser, $template_id, $data, true, $limit);
			}

			$this->log([
				'action' =>  __FUNCTION__,
				'url' =>  $curl->getUrl(),
				'params' => $curl->getData(),
				'content' =>  $curl->getContent(),
				'msg' =>  '模板消息推送失败',
			], 'error');
		} catch (\Throwable $th) {
			$this->log([
				'action' =>  __FUNCTION__,
				'msg' => '客户端错误：' . $th->getMessage(),
				'line' =>  $th->getLine(),
				'file' =>  $th->getFile(),
			], 'error');
		}
		return false;
	}

	/**
	 *   Jssdk授权
	 *    @param string $url 授权url
	 *    @return null
	 */
	public function JsSdk(string $url = "")
	{
		$jsapiTicket = $this->_JsApiTicket();
		$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
		if (!$url) {
			$url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
		}

		$timestamp = time();

		$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
		$nonceStr = "";
		for ($i = 0; $i < 16; $i++) {
			$nonceStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
		}

		// 这里参数的顺序要按照 key 值 ASCII 码升序排序
		$string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";

		$signature = sha1($string);

		$signPackage = [
			"appId" => $this->appid,
			"nonceStr" => $nonceStr,
			"timestamp" => $timestamp,
			"url" => $url,
			"signature" => $signature,
			"rawString" => $string,
		];
		return $signPackage;
	}
}
