<?php
namespace API\Partenaire;

use API\Partenaire\Exception\WsInvalidResponse;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Utils;
use Psr\SimpleCache\CacheInterface;

abstract class BaseOAuth2Ws extends BaseWs
{
    private string $authUrl;

    private string $clientId;
    private string $clientSecret;
    
    private CacheInterface $cache;
    private string $cacheKey;

//_____________________________________________________________________________
// Constructor

    public function __construct(
        string $authUrl,
        string $login,
        string $secret,
        CacheInterface $cache,
        string $cacheKey
    ) {
        $this->authUrl = trim($authUrl, '/');
        $this->clientId = $login;
        $this->clientSecret = $secret;
        $this->cache = $cache;
        $this->cacheKey = $cacheKey;
    }

//_____________________________________________________________________________
// Accessor

    protected function getAuthUrl() :string
    {
        return $this->authUrl;
    }

    protected function getClientId() :string
    {
        return $this->clientId;
    }
    
    protected function getClientSecret() :string
    {
        return $this->clientSecret;
    }

//_____________________________________________________________________________
// Cache

    protected function getAuthTokenFromCache() :?string
    {
        $expiry = $this->cache->get($this->cacheKey . '_EXPIRY');

        // Case : token has expired
        if ($expiry != null
            && new \DateTime('@'.$expiry) < new \DateTime()
        ) {
            $this->cache->deleteMultiple([
                $this->cacheKey,
                $this->cacheKey . '_EXPIRY',
            ]);
            return null;
        }

        return $this->cache->get($this->cacheKey);
    }

    protected function setAuthTokenToCache(string $token, \DateTime $expiry) :self
    {
        $this->cache->set($this->cacheKey, $token);
        $this->cache->set($this->cacheKey . '_EXPIRY', $expiry->getTimestamp());
        
        return $this;
    }

//_____________________________________________________________________________
// Process

    /**
     * @return array<mixed>
     */
    protected function getAuthPayload() :array
    {
        return [
            'grant_type' => 'client_credentials',
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret,
        ];
    }

    protected function getAuthRequest() :Request
    {
        return new Request(
            'POST',
            $this->getAuthUrl(),
            [
                'Accept' => 'application/json',
                'Content-Type' => 'application/x-www-form-urlencoded',
            ],
            http_build_query($this->getAuthPayload())
        );
    }

    /**
     * @return string
     */
    public function getAuthToken(bool $use_cache = true) :string
    {
        // Case : get from cache
        if ($use_cache) {
            $token = $this->getAuthTokenFromCache();
            if ($token !== null) {
                return $token;
            }
        }

        $response = $this->getGuzzleClient()
            ->send($request = $this->getAuthRequest());

        if (! $this->isValidHttpCodeStatus($response->getStatusCode())) {
            throw new WsInvalidResponse($request, $response);
        }

        /** @var object{access_token:string,expire_in:int} */
        /** @var \stdClass */
        $payload = Utils::jsonDecode($response->getBody());

        // Validate payload
        $token = $payload->access_token ?? null;
        if (empty($token)) {
            throw new \Exception('Invalid token response "'.json_encode($payload).'"');
        }

        $expiry = $payload->expires_in ?? null;
        if (!is_int($expiry)) {
            throw new \Exception('Invalid token response "'.json_encode($payload).'"');
        }

        // Update cache
        $this->setAuthTokenToCache($payload->access_token, new \DateTime('+'.$expiry.' second'));
        return $payload->access_token;
    }

    protected function applyBearer(Request $request) :Request
    {
        return $request->withHeader(
            'Authorization',
            'Bearer '.$this->getAuthToken()
        );
    }
}
