<?php

namespace App\libs\wechat\auth\Gateways;

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

class General 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/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
     *
     * @desc    获取授权地址
     *
     * @param string      $scope    授权类型snsapi_base|snsapi_userinfo
     * @param string|null $redirect 授权回跳地址
     */
    public function authUrl(string $scope = 'snsapi_base', string $redirect = null): string
    {
        $redirect = $redirect ?: "https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
        $query    = http_build_query([
            'appid'         => $this->appId,
            'redirect_uri'  => $redirect,
            'response_type' => 'code',
            'scope'         => $scope,
        ]);

        return "https://open.weixin.qq.com/connect/oauth2/authorize?{$query}#wechat_redirect";
    }

    /**
     * @see    第二步 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
     *
     * @desc    使用授权code换取openid
     */
    public function code2Openid(string $code): array
    {
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'appid'      => $this->appId,
            'secret'     => $this->secret,
            'code'       => $code,
            'grant_type' => 'authorization_code',
        ]));

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

    /**
     * @see    第四步 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
     *
     * @desc    获取用户信息
     *
     * @return bool|mixed|string
     */
    public function userSimpleInfo(string $accessToken, string $openid): array
    {
        $url = 'https://api.weixin.qq.com/sns/userinfo';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'access_token' => $accessToken,
            'openid'       => $openid,
            'lang'         => 'zh_CN',
        ]));

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

    /**
     * @see    https://developers.weixin.qq.com/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html#UinonId
     *
     * @desc    获取用户详细信息
     */
    public function userDetailInfo(string $openid): array
    {
        $url = 'https://api.weixin.qq.com/cgi-bin/user/info';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'access_token' => $this->accessToken(),
            'openid'       => $openid,
            'lang'         => 'zh_CN',
        ]));

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

    /**
     * @see    附录1-JS-SDK使用权限签名算法 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
     *
     * @desc    获取 JSAPI TICKET
     *
     * @param int $repeatCnt
     */
    public function jsApiTicket(&$repeatCnt = 1): string
    {
        $drive = $this->config['token_drive'] ?? 'redis';
        $key   = 'wechat_' . $this->appId . '_jsApiTicket';

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

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

                break;

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

                break;
        }
        if ($ticket) {
            return $ticket;
        }

        $url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'access_token' => $this->accessToken(),
            'type'         => 'jsapi',
        ]));
        $data = json_decode($res, true);
        if (isset($data['ticket'])) {
            $ticket = $data['ticket'];

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

                    break;

                case 'db':
                default:
                    Db::table('configs')
                        ->where('c_key', $key)
                        ->update([
                            'c_value'   => $ticket,
                            'c_expired' => time() + 7000,
                            'c_updated' => date('Y-m-d H:i:s'),
                        ])
                    ;

                    break;
            }

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

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

        return $data['errcode'];
    }

    /**
     * @see    https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
     *
     * @desc    获取全局唯一接口调用凭据
     *
     * @param int $repeatCnt
     *
     * @return string
     */
    public function accessToken(&$repeatCnt = 1)
    {
        $drive = $this->config['token_drive'] ?? 'redis';
        $key   = 'wechat_' . $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'];
    }
}
