<?php

namespace YooMoney\Model;

use Exception;
use Psr\Log\LoggerInterface;
use YooMoney\Helpers\OauthHelpers\HttpClientInterface;
use YooMoney\Helpers\OauthHelpers\OauthDataStorageHelperInterface;
use YooMoney\Helpers\OauthHelpers\OauthWebhooksHelperInterface;
use YooMoney\Helpers\OauthHelpers\YookassaShopInfoHelperInterface;

/**
 * Класс для работы с oauth
 */
class OauthModel
{
    const OAUTH_CMS_PATH = '/integration/oauth-cms';

    const AUTHORIZATION = 'authorization';
    const GET_TOKEN = 'get-token';
    const REVOKE_TOKEN = 'revoke-token';

    const ACCESS_TOKEN_KEY = 'access_token';
    const EXPIRES_IN_TOKEN_KEY = 'expires_in';

    const CMS_NAME = 'joomla';

    /** @var LoggerInterface */
    private $logger;

    /** @var OauthDataStorageHelperInterface */
    private $storageHelper;

    /** @var OauthWebhooksHelperInterface */
    private $webhooksHelper;

    /** @var YookassaShopInfoHelperInterface */
    private $shopInfoHelper;

    /** @var HttpClientInterface */
    private $httpClient;

    public function __construct(
        LoggerInterface $logger,
        OauthDataStorageHelperInterface $storageHelper,
        OauthWebhooksHelperInterface $webhooksHelper,
        YookassaShopInfoHelperInterface $shopInfoHelper,
        HttpClientInterface $httpClient
    )
    {
        $this->logger = $logger;
        $this->storageHelper = $storageHelper;
        $this->webhooksHelper = $webhooksHelper;
        $this->shopInfoHelper = $shopInfoHelper;
        $this->httpClient = $httpClient;
    }

    /**
     * Формирование данных, отправка запроса на получение ссылки для авторизации
     * и получение результата запроса
     *
     * @return array - массив для последующей кодировки в JSON для передачи в JS
     * @throws Exception
     */
    public function getOauthConnectUrl()
    {
        $requestParameters = array(
            'state' => $this->getOauthState(),
            'cms' => self::CMS_NAME,
            'host' => $_SERVER['HTTP_HOST']
        );

        $url = self::OAUTH_CMS_PATH . '/' . self::AUTHORIZATION;

        $this->logger->log(
            'info', 'Making a request for OAuth link: ' . $url . ' : ' . json_encode($requestParameters)
        );

        try {
            $response = $this->httpClient->sendRequest($url, $requestParameters);
        } catch (Exception $e) {
            $this->logger->log('error', 'Failed to get OAuth link: ' . $e->getMessage());
            return array('error' => 'Got error while getting OAuth link.');
        }

        $this->logger->log('info', 'Got response from ' . $url . ' : ' . $response);

        $responseData = json_decode($response, true);

        if (empty($responseData['oauth_url'])) {
            $error = empty($responseData['error']) ? 'OAuth URL not found' : $responseData['error'];
            $this->logger->log('error', 'Got error while getting OAuth link: ' . $error);
            throw new Exception($error);
        }

        return array('oauth_url' => $responseData['oauth_url']);
    }

    /**
     * Формирует параметры, инициирует отправку, получает результат запроса на получение токена,
     * проверяет ответ на запрос, инициирует сохранение токена в БД
     *
     * @return array - массив для последующей кодировки в JSON для передачи в JS
     * @throws Exception
     */
    public function getOauthToken()
    {
        $this->logger->sendHeka(array('oauth.get-token.init'));

        $requestParameters = array(
            'state' => $this->storageHelper->getState()
        );

        $url = self::OAUTH_CMS_PATH . '/' . self::GET_TOKEN;

        $this->logger->log(
            'info', 'Making a request for OAuth token: ' . $url . ' : ' . json_encode($requestParameters)
        );

        try {
            $response = $this->httpClient->sendRequest($url, $requestParameters);
        } catch (Exception $e) {
            $this->logger->sendHeka(array('oauth.get-token.fail'));
            $this->logger->log('error', 'Failed to get OAuth token: ' . $e->getMessage());
            throw new Exception('Got error while getting OAuth token.');
        }

        $this->logger->log('info', 'Got response from ' . $url . ' : ' . $response);

        $responseData = json_decode($response, true);

        if (empty($responseData[self::ACCESS_TOKEN_KEY])) {
            $this->logger->sendHeka(array('oauth.get-token.fail'));
            $error = empty($data['error']) ? 'OAuth token not found' : $data['error'];
            $this->logger->log('error', 'Got error while getting OAuth token: ' . $error);
            throw new Exception($error);
        }

        $expiresIn = empty($responseData[self::EXPIRES_IN_TOKEN_KEY]) ? null : $responseData[self::EXPIRES_IN_TOKEN_KEY];

        $this->revokeOldToken();

        if (!$this->storageHelper->saveToken($responseData[self::ACCESS_TOKEN_KEY], $expiresIn)) {
            $error = 'OAuth token was not saved!';
            $this->logger->log('error', $error);
            throw new Exception($error);
        }
        $this->logger->log('info', 'OAuth token saved successfully');

        try {
            $this->logger->sendHeka(array('webhooks.subscribe.init'));
            $notificationUrl = $this->webhooksHelper->getNotificationUrl();
            $this->webhooksHelper->addWebhooksWithGivenToken($responseData[self::ACCESS_TOKEN_KEY], $notificationUrl);
            $this->logger->sendHeka(array('webhooks.subscribe.success'));
        } catch (Exception $e) {
            $this->logger->sendHeka(array('webhooks.subscribe.fail', 'oauth.get-token.fail'));
            $this->logger->log('error', 'Failed to make webhooks: ' . $e->getMessage());
            throw new Exception('Failed to make webhooks');
        }

        try {
            $this->logger->sendHeka(array('oauth.get-shop.init'));
            $shopId = $this->shopInfoHelper->fetchShopIdWithGivenToken($responseData[self::ACCESS_TOKEN_KEY]);
        } catch (Exception $e) {
            $this->logger->log('error', $e->getMessage());
            $this->logger->sendHeka(array('oauth.get-shop.fail', 'oauth.get-token.fail'));
            throw new Exception('Got error while getting shop info! ' . $e->getMessage());
        }
        if (!$this->storageHelper->saveShopId($shopId)) {
            $error = 'Shop Id was not saved!';
            $this->logger->log('error', $error);
            $this->logger->sendHeka(array('oauth.get-shop.fail', 'oauth.get-token.fail'));
            throw new Exception($error);
        }
        $this->logger->log('info', 'Shop Id saved successfully');
        $this->logger->sendHeka(array('oauth.get-shop.success', 'oauth.get-token.success'));

        return array('success' => true);
    }

    /**
     * Проверяет в БД state и возвращает его, если нет в БД, генерирует его
     *
     * @return string|void
     */
    public function getOauthState()
    {
        $state = $this->storageHelper->getState();

        if (!$state) {
            $state = substr(md5(time()), 0, 12);
            $this->storageHelper->saveState($state);
        }

        return $state;
    }

    /**
     * Выполняет запрос в OAuth приложение на отзыв токена
     *
     * @return void
     */
    public function revokeOldToken()
    {
        $token = $this->storageHelper->getToken();
        if (!$token) {
            $this->logger->log('info', 'Old token not found. Skip revoking.');
            return;
        }

        try {
            $this->logger->sendHeka(array('webhooks.remove.init'));
            $this->webhooksHelper->removeWebhooksWithGivenToken($token);
            $this->logger->sendHeka(array('webhooks.remove.success'));
        } catch (Exception $e) {
            $this->logger->sendHeka(array('webhooks.remove.fail'));
            $this->logger->log('error', 'Got error while deleting webhooks. ' . $e->getMessage());
            return;
        }

        $requestParameters = array(
            'state' => $this->storageHelper->getState(),
            'token' => $token,
            'cms' => self::CMS_NAME
        );

        $url = self::OAUTH_CMS_PATH . '/' . self::REVOKE_TOKEN;

        $this->logger->log('info', 'Sending request to revoke OAuth token. ' . $url . ' : ' . json_encode($requestParameters));

        try {
            $this->logger->sendHeka(array('token.revoke.init'));
            $result = $this->httpClient->sendRequest($url, $requestParameters);
        } catch (Exception $e) {
            $this->logger->sendHeka(array('token.revoke.fail'));
            $this->logger->log('error', 'Got error while revoking OAuth token. ' . $e->getMessage());
            return;
        }

        $this->logger->log('info', 'Response from ' . $url . ' : ' . $result);

        $body = json_decode($result, true);

        if (!empty($body['error'])) {
            $this->logger->sendHeka(array('token.revoke.fail'));
            $this->logger->log('error', 'Got error while revoking OAuth token.');
            return;
        }
        $this->logger->sendHeka(array('token.revoke.success'));
        $this->logger->log('info', 'Token ' . $token . ' revoked successfully!');
    }
}
