<?php
/**
 * Webkul Software
 *
 * @category  Webkul
 * @package   Webkul_MpPaypal
 * @author    Webkul
 * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com)
 * @license   https://store.webkul.com/license.html
 */
namespace Webkul\MpPaypal\Model\Paypal;

use Magento\Payment\Gateway\Http\ClientException;
use Firebase\JWT\JWT;
use Magento\Framework\App\Config\ScopeConfigInterface;

class Adapter
{
    const SANDBOX_URL = 'https://api.sandbox.paypal.com/';
    const LIVE_URL = 'https://api.paypal.com/';
    const ACCESS_TOKEN_URI = 'v1/oauth2/token';
    const INTEGRATION_SIGNUP = 'v2/customer/partner-referrals';
    const CHECKOUT_ORDERS = 'v2/checkout/orders';
    const WEBHOOK_URL = 'v1/notifications/webhooks';
    const PAYOUT_URL = 'v1/payments/referenced-payouts-items';
    const MERCHANT_INTEGRATION = 'v1/customer/partners';
    const PAYMENT_CAPTURES = 'v2/payments/captures';
    const ORDER_DETAILS = 'v2/checkout/orders';

    public static $_accessToken;
    public static $_expireIn;

    /**
     * @var \Webkul\MpPaypal\Gateway\Config\Config
     */
    protected $_paymentConfig;

    /**
     * @var \Magento\Framework\HTTP\Client\Curl
     */
    protected $_curl;

    /**
     * @var Magento\Framework\Json\Helper\Data
     */
    protected $jsonHelper;

    public function __construct(
        \Webkul\MpPaypal\Gateway\Config\Config $paymentConfig,
        \Magento\Customer\Model\SessionFactory $session,
        \Webkul\MpPaypal\Model\MpPaypalFactory $paypalOrders,
        \Magento\Framework\HTTP\Client\Curl $curl,
        \Magento\Framework\Stdlib\DateTime\DateTime $date,
        \Magento\Framework\UrlInterface $urlInterface,
        \Magento\Framework\App\Config\Storage\WriterInterface $configWriter,
        \Magento\Framework\Url $urlHelper,
        \Magento\Framework\Encryption\EncryptorInterface $encryptor,
        \Webkul\MpPaypal\Helper\Data $helper,
        \Magento\Framework\Json\Helper\Data $jsonHelper
    ) {
        $this->_paymentConfig = $paymentConfig;
        $this->paypalOrders = $paypalOrders;
        $this->_urlInterface = $urlInterface;
        $this->configWriter = $configWriter;
        $this->urlHelper = $urlHelper;
        $this->session = $session;
        $this->_curl = $curl;
        $this->jsonHelper = $jsonHelper;
        $this->_date = $date;
        $this->_encryptor = $encryptor;
        $this->helper = $helper;
    }

    /**
     * get api url
     *
     * @param string $uri
     * @return string
     */
    public function getApiUrl($uri)
    {
        if ($this->_paymentConfig->getEnvironment() == 'sandbox') {
            return self::SANDBOX_URL.$uri;
        }
        
        return self::LIVE_URL.$uri;
    }

    /**
     * get bearer access token
     *
     * @return void
     */
    public function getAccessToken()
    {
        $currentTime = time();
        $expiresIn = $this->_paymentConfig->getExpiresIn();
        $accessToken = $this->_paymentConfig->getAccessToken();
        if ($accessToken && $currentTime < $expiresIn) {
            return $accessToken;
        }
        
        $accessTokenUri = $this->getApiUrl(self::ACCESS_TOKEN_URI);
        $clientId = $this->_encryptor->decrypt($this->_paymentConfig->getClientId());
        $clientSecret = $this->_encryptor->decrypt($this->_paymentConfig->getClientSecret());
        $this->_curl->setCredentials($clientId, $clientSecret);
        $this->_curl->addHeader('Content-Type', 'application/x-www-form-urlencoded');
        $this->_curl->addHeader('Accept', 'application/json');
        $this->_curl->addHeader('Accept-Language', 'en_US');
        $bodyParams = [
            'grant_type' => 'client_credentials'
        ];
        $this->_curl->post($accessTokenUri, $bodyParams);
        $responseBody = $this->_curl->getBody();
        if ($responseBody) {
            $responseData = $this->jsonHelper->jsonDecode($responseBody, true);
            if (isset($responseData['access_token'])) {
                self::$_accessToken = $responseData['access_token'];
                self::$_expireIn = $currentTime+$responseData['expires_in'];
                $this->configWriter->save(
                    'payment/mppaypal/access_token',
                    $responseData['access_token'],
                    $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
                    $scopeId = \Magento\Store\Model\ScopeInterface::SCOPE_STORE
                );
                $this->configWriter->save(
                    'payment/mppaypal/expires_in',
                    $responseData['expires_in'],
                    $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
                    $scopeId = \Magento\Store\Model\ScopeInterface::SCOPE_STORE
                );
            } else {
                throw new ClientException(__("Client authentication failed"));
            }
            
        }

        return self::$_accessToken;
    }

    /**
     * get merchant status
     *
     * @param string $merchantId
     * @return void
     */
    public function getMerchantStatus($merchantId)
    {
        /**
         * ToDo get status from paypal test api
         */
        if ($merchantId) {
            $partnerId = $this->_paymentConfig->getPartnerMerchantId();
            $sellerStatusApi = $this->getApiUrl(
                self::MERCHANT_INTEGRATION
            ).'/'.$partnerId.'/merchant-integrations/'.$merchantId;
            $accessToken = $this->getAccessToken();
            $this->_curl->addHeader('Content-Type', 'application/json');
            $this->_curl->addHeader('Authorization', 'Bearer '.$accessToken);
            $clientId = $this->_encryptor->decrypt($this->_paymentConfig->getClientId());
            
            $clientSecret = $this->_encryptor->decrypt($this->_paymentConfig->getClientSecret());
            $this->_curl->setCredentials($clientId, $clientSecret);
            $this->_curl->get($sellerStatusApi);
            $responseBody = $this->_curl->getBody();
            return $responseBody;
        }
        return false;
    }

    /**
     * get referral url
     *
     * @return string
     */
    public function getPartnerRefferalUrl()
    {
        /**
         * todo get referral url from paypal api
         */
        $customer = $this->session->create()->getCustomer();
        $accessToken = $this->getAccessToken();
        $clientId = $this->_encryptor->decrypt($this->_paymentConfig->getClientId());
        $partnerReferalUrl = $this->getApiUrl(self::INTEGRATION_SIGNUP);
        $this->_curl->addHeader('Content-Type', 'application/json');
        $this->_curl->addHeader('Authorization', 'Bearer '.$accessToken);
        /**
         * customer body params
         */
        $bodyParams = [
            "tracking_id" => $customer->getId(),
            "email" => $customer->getEmail(),
            "operations"=> [
                [
                    "operation" => "API_INTEGRATION",
                    "api_integration_preference" => [
                        "rest_api_integration" => [
                            "integration_method" => "PAYPAL",
                            "integration_type" => "THIRD_PARTY",
                            "third_party_details" => [
                                "features" => [
                                    "PAYMENT",
                                    "REFUND",
                                    "PARTNER_FEE",
                                    "DELAY_FUNDS_DISBURSEMENT"
                                ]
                            ]
                        ]
                    ]
                ]
            ],
            "products" => [
                "PPCP"
            ],
            "legal_consents" => [
                [
                    "type" => "SHARE_DATA_CONSENT",
                    "granted" => true
                ]
            ],
            "partner_config_override" => [
                "return_url" => $this->_urlInterface->getUrl('mppaypal/seller/index'),
                "return_url_description" => __('The url to return the merchant after the paypal onboarding process.')
            ]
        ];
        $referalUrl = '';
        $this->_curl->post($partnerReferalUrl, $this->jsonHelper->jsonEncode($bodyParams));
        $responseBody = $this->_curl->getBody();
        if ($responseBody) {
            $responseData = $this->jsonHelper->jsonDecode($responseBody, true);
            if (isset($responseData['links'])) {
                foreach ($responseData['links'] as $response) {
                    if ($response['rel'] == 'action_url') {
                        $referalUrl = $response['href'];
                    }
                }
            } else {
                throw new ClientException(__("Client authentication failed"));
            }
            
        }
        return $referalUrl;
    }

    /**
     * create create order request
     */
    public function _create($bodyParams)
    {
        $accessToken = $this->getAccessToken();
        $createOrderUri = $this->getApiUrl(self::CHECKOUT_ORDERS);
        $this->_curl->addHeader('Content-Type', 'application/json');
        $this->_curl->addHeader('Authorization', 'Bearer '.$accessToken);
        $this->_curl->addHeader('PayPal-Partner-Attribution-Id', $this->_paymentConfig->getApplication());
        
        $this->_curl->post($createOrderUri, $this->jsonHelper->jsonEncode($bodyParams['original']));
        $responseBody = $this->_curl->getBody();
        return $responseBody;
    }

    /**
     * to capture created order
     *
     * @param string $orderID
     * @return array
     */
    public function captureOrder($data)
    {
        $orderID = $data['paypal_order_id'];
        $accessToken = $this->getAccessToken();
        $captureOrderApi = $this->getApiUrl(self::CHECKOUT_ORDERS).'/'.$orderID.'/capture';
        $this->_curl->addHeader('Content-Type', 'application/json');
        $this->_curl->addHeader('Authorization', 'Bearer '.$accessToken);
        $this->_curl->addHeader('PayPal-Partner-Attribution-Id', $this->_paymentConfig->getApplication());

        $this->_curl->post($captureOrderApi, []);
        $responseBody = $this->_curl->getBody();
        $this->helper->logDataInLogger('capture response'.$responseBody);
        $responseBody = $this->jsonHelper->jsonDecode($responseBody, true);
        if (isset($responseBody['debug_id'])) {
            $message = __($responseBody['message'] ?: 'Sorry, but something went wrong');
            $this->helper->logDataInLogger($message);
            throw new ClientException($message);
        }

        $collection = $this->paypalOrders->create()->getCollection()
        ->addFieldToFilter('paypal_order_id', $data['paypal_order_id'])->getFirstItem();
        if ($collection->getId()) {
            $captureId = $responseBody['purchase_units']['0']['payments']['captures'][0]['id'];
            $collection->setCaptureId($captureId);
            $collection->setUpdatedAt($this->_date->gmtDate());
            $collection->save();
        }
        return $responseBody;
    }

    /**
     * to create webhook at paypal
     *
     * @return array
     */
    public function generateWebHook()
    {
        $accessToken = $this->getAccessToken();
        $createWebhookUri = $this->getApiUrl(self::WEBHOOK_URL);
        $this->_curl->addHeader('Content-Type', 'application/json');
        $this->_curl->addHeader('Authorization', 'Bearer '.$accessToken);
        /**
         * list webhooks
         */

        $bodyParams = [
            'url' => $this->urlHelper->getUrl(
                '',
                [
                    '_nosid' => true
                ]
            ).'mppaypal/seller/webhook',
            'event_types' => [
                ['name' => 'MERCHANT.ONBOARDING.COMPLETED']
            ]
        ];
        $this->_curl->post($createWebhookUri, $this->jsonHelper->jsonEncode($bodyParams));
        $responseBody = $this->_curl->getBody();
        return $this->jsonHelper->jsonDecode($responseBody);
    }
    
    public function cancelOrder($order)
    {
        if ($order != null) {
            $message = __('Order cancelled, because paypal payment failed');

            try {
                $order->cancel();
                $order->registerCancellation($message);
                $history = $order->addStatusHistoryComment($message, false);
                $history->setIsCustomerNotified(false);
                $order->save();
            } catch (\Exception $e) {
                $this->helper->logDataInLogger("Controller_Index_Paymentsuccess declineOrder : ".$e->getMessage());
            }
        }
    }

    public function refundOrder($data, $purchaseUnit)
    {
        $captureData = $purchaseUnit['payments']['captures'][0];
        $payee = $purchaseUnit['payee']['merchant_id'];
        $clientId = $this->_encryptor->decrypt($this->_paymentConfig->getClientId());

        $captureOrderRefundApi = $this->getApiUrl(self::PAYMENT_CAPTURES).'/'.$captureData['id'].'/refund';
        $accessToken = $this->getAccessToken();
        $this->_curl->addHeader('Content-Type', 'application/json');
        $this->_curl->addHeader('Authorization', 'Bearer '.$accessToken);
        $this->_curl->addHeader('PayPal-Partner-Attribution-Id', $this->_paymentConfig->getApplication());

        $tokenData['iss'] = $clientId;
        $tokenData['payer_id'] = $payee;
        $jwtToken = JWT::encode($tokenData, '');
        $this->_curl->addHeader('PayPal-Auth-Assertion', $jwtToken);
        $this->_curl->post($captureOrderRefundApi, $this->jsonHelper->jsonEncode($data));
        $responseBody = $this->_curl->getBody();
        $this->helper->logDataInLogger('capture refund response'.$responseBody);
        $responseBody = $this->jsonHelper->jsonDecode($responseBody, true);
        if (isset($responseBody['debug_id'])) {
            $message = __($responseBody['message'] ?: 'Sorry, but something went wrong');
            $this->helper->logDataInLogger($message);
            throw new ClientException($message);
        }
        return $responseBody;
    }

    public function findOrder($orderId)
    {
        $captureOrderApi = $this->getApiUrl(self::ORDER_DETAILS).'/'.$orderId;
        $accessToken = $this->getAccessToken();
        $this->_curl->addHeader('Content-Type', 'application/json');
        $this->_curl->addHeader('Authorization', 'Bearer '.$accessToken);
        $this->_curl->get($captureOrderApi);
        $responseBody = $this->_curl->getBody();
        $this->helper->logDataInLogger('order details response'.$responseBody);
        $responseBody = $this->jsonHelper->jsonDecode($responseBody, true);
        if (isset($responseBody['debug_id'])) {
            $message = __($responseBody['message'] ?: 'Sorry, but something went wrong');
            $this->helper->logDataInLogger($message);
            throw new ClientException($message);
        }
        return $responseBody;
    }

    public function releasePayment($captureId)
    {
        $data['reference_type'] = "TRANSACTION_ID";
        $data['reference_id'] = $captureId;
        $payoutApi = $this->getApiUrl(self::PAYOUT_URL);
        $accessToken = $this->getAccessToken();
        $this->_curl->addHeader('Content-Type', 'application/json');
        $this->_curl->addHeader('Authorization', 'Bearer '.$accessToken);
        $this->_curl->addHeader('PayPal-Partner-Attribution-Id', $this->_paymentConfig->getApplication());
        $this->_curl->post($payoutApi, $this->jsonHelper->jsonEncode($data));
        $responseBody = $this->_curl->getBody();
        $this->helper->logDataInLogger('payout details response'.$responseBody);
        $responseBody = $this->jsonHelper->jsonDecode($responseBody, true);
        if (isset($responseBody['debug_id'])) {
            $message = __($responseBody['message'] ?: 'Sorry, but something went wrong');
            $this->helper->logDataInLogger($message);
            throw new ClientException($message);
        }
        if ($responseBody['processing_state']['status'] == 'SUCCESS') {
            return $responseBody;
        } else {
            $message = __('Sorry, but something went wrong');
            $this->helper->logDataInLogger($message);
            throw new ClientException($message);
        }
    }
}
