<?php

declare(strict_types=1);

/**
 * Created by Aurélien RICHAUD (31/07/2019 11:03).
 */
require_once __DIR__
    . \DIRECTORY_SEPARATOR . '..'
    . \DIRECTORY_SEPARATOR . '..'
    . \DIRECTORY_SEPARATOR . 'classes' . \DIRECTORY_SEPARATOR . 'OAuthAuthCode.php';
require_once __DIR__
    . \DIRECTORY_SEPARATOR . '..'
    . \DIRECTORY_SEPARATOR . '..'
    . \DIRECTORY_SEPARATOR . 'classes' . \DIRECTORY_SEPARATOR . 'OAuthAccessToken.php';
require_once __DIR__
    . \DIRECTORY_SEPARATOR . '..'
    . \DIRECTORY_SEPARATOR . '..'
    . \DIRECTORY_SEPARATOR . 'classes' . \DIRECTORY_SEPARATOR . 'OAuthClient.php';

class MyvetshopcliniqueTokenModuleFrontController extends FrontController
{
    /**
     * @return array{
     *     access_token: string,
     *     expires_in: int,
     *     token_type: string,
     *     scope: string
     * }
     */
    protected function getToken(): array
    {
        // ///////////////////////////////////////
        // Récupération des informations OAuth
        $code = Tools::getValue('code');
        $grant_type = Tools::getValue('grant_type');
        $client_id = Tools::getValue('client_id');
        $client_secret = Tools::getValue('client_secret');
        $redirect_uri = Tools::getValue('redirect_uri');

        // Paramètres obligatoires
        if (
            !\is_string($code)
            || !\is_string($grant_type)
            || !\is_string($client_id)
            || !\is_string($client_secret)
            || !\is_string($redirect_uri)
            || !$code
            || !$grant_type
            || !$client_id
            || !$client_secret
            || !$redirect_uri
        ) {
            throw new Exception('Argument missing (code|grant_type|client_id|client_secret|redirect_uri)', 400);
        }

        try {
            $authCode = OAuthAuthCode::getByToken($code);
            $client = OAuthClient::getByRandomId($client_id);
        } catch (PrestaShopDatabaseException|PrestaShopException $e) {
            $authCode = null;
            $client = null;
        }

        // AuthCode invalide
        if (!$authCode || !$client || $authCode->client_id != $client->id_oauth_client) {
            throw new Exception('Something not found', 500);
        }

        // Client secret
        if ($client->secret != $client_secret) {
            throw new Exception('Invalid client secret', 403);
        }

        // RedirectUri invalide
        if (!\in_array($redirect_uri, \explode(',', $client->redirect_uris))) {
            throw new Exception('Invalid redirect uri', 403);
        }

        // Grant type invalide
        if ('bearer' != \strtolower($grant_type)) {
            throw new Exception('Invalid grant type', 403);
        }

        // Création d'un AccessToken
        $accessToken = new OAuthAccessToken();
        $accessToken->client_id = (string) $authCode->client_id;
        $accessToken->id_customer = $authCode->id_customer;
        $accessToken->redirect_uri = $redirect_uri;
        $accessToken->created = \time();
        $accessToken->expires = \time() + 365 * 24 * 60 * 60; // 1 an

        // Supprime l'AuthCode
        try {
            $accessToken->save();
            $authCode->delete();
        } catch (PrestaShopException $e) {
            throw new Exception($e->getMessage(), 500, $e);
        }

        $context = Context::getContext();
        \assert(null !== $context);

        // Création du Cookie OAuth associé
        $accessTokenCookie = new OAuthAccessTokenCookie();
        $accessTokenCookie->token = $accessToken->token;
        $accessTokenCookie->name = $context->cookie->getName();
        $accessTokenCookie->expires = $accessToken->expires;
        $accessTokenCookie->add();

        // ///////////////////////////////////////
        // / Redirige vers l'URL de retour
        return [
            'access_token' => $accessToken->token,
            'expires_in' => $accessToken->expires - \time(),
            'token_type' => 'Bearer',
            'scope' => '',
        ];
    }

    /**
     * @return array{
     *     access_token: string,
     *     expires_in: int,
     *     token_type: string,
     *     scope: string
     * }
     */
    protected function deleteToken(): array
    {
        $body = \file_get_contents('php://input');
        if ($body) {
            \parse_str($body, $vars);
        } else {
            $vars = [];
        }

        // Déconnexion
        if (!isset($vars['access_token'])) {
            $accessTokenRaw = Tools::getValue('access_token');

            $token = \is_string($accessTokenRaw) ? $accessTokenRaw : '';
        } else {
            $token = \is_string($vars['access_token']) ? $vars['access_token'] : '';
        }

        $accessToken = OAuthAccessToken::getByToken($token);
        // Si token non trouvé, refuse l'accès
        if (!$accessToken) {
            throw new Exception('Token not found', 403);
        }

        // Indique la date d'expiration du token si celui-ci n'est pas déjà expiré
        if ($accessToken->expires > \time()) {
            $accessToken->expires = \time();
            $accessToken->save();
        }

        // Indique le token est expiré
        return [
            'access_token' => $accessToken->token,
            'expires_in' => $accessToken->expires - \time(),
            'token_type' => 'Bearer',
            'scope' => '',
        ];
    }

    public function run(): void
    {
        $this->init();

        try {
            $ret = isset($_SERVER['REQUEST_METHOD']) && 'DELETE' === $_SERVER['REQUEST_METHOD']
                ? $this->deleteToken()
                : $this->getToken();
        } catch (Exception $e) {
            switch ($e->getCode()) {
                case 400:
                    header('HTTP/1.0 400 Bad Request');
                    break;
                case 403:
                    header('HTTP/1.0 403 Forbidden');
                    break;
                default:
                    header('HTTP/1.0 500 Internal Server Error');
            }

            $ret = [
                'errors' => [$e->getMessage()],
            ];
        }

        $this->ajaxRender(\json_encode($ret) ?: null);
    }

    /**
     * Réécriture de la méthode display (pas besoin de rendre un template, le controller doit etre utilisé en ajax
     */
    public function display(): bool
    {
        return true;
    }
}
