<?php

declare(strict_types=1);

namespace App\Utils;

use App\Dictionary\WeChat\Official\OpenApi;
use EasyWeChat\Kernel\Contracts\RefreshableAccessToken;
use HttpException;
use Illuminate\Support\Facades\Redis;
use JsonException;
use JetBrains\PhpStorm\ArrayShape;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;

class CentralControlAccessToken implements RefreshableAccessToken
{
    public function __construct(){}

    /**
     * Access Token Save Key.
     *
     * @return string
     */
    public function getKey(): string
    {
        return 'mini_platform:access_token';
    }

    /**
     * Get Access Token.
     *
     * @return string
     *
     * @throws ClientExceptionInterface
     * @throws ContainerExceptionInterface
     * @throws DecodingExceptionInterface
     * @throws HttpException
     * @throws JsonException
     * @throws NotFoundExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ServerExceptionInterface
     * @throws TransportExceptionInterface
     */
    public function getToken(): string
    {
        $token = Redis::get($this->getKey());

        if ($token && is_string($token)) {
            return $token;
        }

        return $this->refresh();
    }

    /**
     * Get Access Token For Query Params.
     *
     * @return array<string, string>
     *
     * @throws ClientExceptionInterface
     * @throws ContainerExceptionInterface
     * @throws DecodingExceptionInterface
     * @throws HttpException
     * @throws JsonException
     * @throws NotFoundExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ServerExceptionInterface
     * @throws TransportExceptionInterface
     */
    #[ArrayShape(['access_token' => "string"])]
    public function toQuery(): array
    {
        return ['access_token' => $this->getToken()];
    }

    /**
     * @return string
     *
     * @throws HttpException
     * @throws JsonException
     * @throws ClientExceptionInterface
     * @throws DecodingExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ServerExceptionInterface
     * @throws TransportExceptionInterface
     */
    public function refresh(): string
    {
        $client = HttpClient::create(['base_uri' => 'https://api.weixin.qq.com/']);
        $response = $client->request(
            'GET',
            OpenApi::GET_ACCESS_TOKEN->value,
            [
                'query' => [
                    'grant_type' => 'client_credential',
                    'appid' => config('wechat.mini.app_id'),
                    'secret' =>config('wechat.mini.secret'),
                ],
            ]
        )->toArray(false);

        if (empty($response['access_token'])) {
            throw new HttpException('Failed to get access_token: '. json_encode($response, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE)
            );
        }

        Redis::setex($this->getKey(), (int)$response['expires_in'] - 300, $response['access_token']);

        return $response['access_token'];
    }
}