<?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;
use Webkul\MpPayPal\Model\Source\AllowedCountries;

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';
    const GENERATE_TOKEN = 'v1/identity/generate-token';
    const WEBHOOK_DELETE_URL = 'v1/notifications/webhooks';

    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\Customer\Model\CustomerFactory $customerFactory,
        \Magento\Customer\Model\AddressFactory $addressFactory,
        \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->_customerFactory = $customerFactory;
        $this->_addressFactory = $addressFactory;
        $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 string
     */
    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');
        $this->_curl->addHeader('PayPal-Partner-Attribution-Id', $this->_paymentConfig->getApplication());
        $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',
                    (int) $currentTime + (int) $responseData['expires_in'],
                    $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
                    $scopeId = \Magento\Store\Model\ScopeInterface::SCOPE_STORE
                );
            } else {
                return false;
            }
            
        }

        return self::$_accessToken;
    }

    public function generateClientToken()
    {
        $accessToken = $this->getAccessToken();
        if ($accessToken) {
            $generateToken = $this->getApiUrl(self::GENERATE_TOKEN);
            $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($generateToken, []);
            $responseBody = $this->_curl->getBody();
            $responseBody = $this->jsonHelper->jsonDecode($responseBody, true);
            return $responseBody['client_token'];
        }
        return '';
    }

    /**
     * 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();
            if ($accessToken) {
                $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;
        }
        return false;
    }

    /**
     * get referral url
     *
     * @return string
     */
    public function getPartnerRefferalUrl($countryCode)
    {
        /**
         * todo get referral url from PayPal api
         */

        $referalData = [];
        $referalData['url'] = '';
        if ($countryCode) {
            $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);
            $this->_curl->addHeader('PayPal-Partner-Attribution-Id', $this->_paymentConfig->getApplication());

            /**
             * customer body params
             */
            $product = "PPCP";
            
            if (in_array($countryCode, AllowedCountries::BRANDED_COUNTRIES)) {
                $product = "EXPRESS_CHECKOUT";
            }
            $addressArray = [];

            $addressArray['country_code'] = $countryCode;
            $addressArray['type'] = "HOME";
            $customerData = $this->_customerFactory->create()->load($customer->getId());
            $billingAddressId = $customerData->getDefaultBilling();
            $billingAddress = $this->_addressFactory->create()->load($billingAddressId);
            if ($billingAddress->getCity() != null) {
                $addressArray["address_line_1"] = implode(
                    ',',
                    $billingAddress->getStreet()
                );
                $addressArray["address_line_2"] = $billingAddress->getCity();
                $addressArray["admin_area_2"] = $billingAddress->getCity();
                $addressArray["admin_area_1"] = $billingAddress->getCity();
                $addressArray["postal_code"] = $billingAddress->getPostcode();
            }
            $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",
                                        "READ_SELLER_DISPUTE",
                                        "UPDATE_SELLER_DISPUTE"
                                    ]
                                ]
                            ]
                        ]
                    ]
                ],
                "products" => [
                    $product
                ],
                "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.'
                    )
                ],
                "individual_owners" => [
                    [
                        "addresses" => [
                            $addressArray
                        ],
                        "type" => "PRIMARY"
                    ]
                ]
            ];
            $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') {
                            $referalData['url'] = $response['href'];
                        }
                    }
                } else {
                    $referalData['message'] = __('Client authentication failed');
                }
                
            }
        }
        
        return $referalData;
    }

    /**
     * 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));
        $responseBody = $this->_curl->getBody();
        return $responseBody;
    }

    /**
     * to capture created order
     *
     * @param string $orderID
     * @return array
     */
    public function captureOrder($data)
    {
        $this->helper->logDataInLogger('captureOrder method');
        $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);
        }

        foreach ($responseBody['purchase_units'] as $purchaseUnits) {
            $sellerId = explode('sellerId_', $purchaseUnits['reference_id'])[1];
            $this->updateSellerData(
                $purchaseUnits['payments']['captures'][0]['id'],
                $sellerId,
                $data['paypal_order_id']
            );
        }
        return $responseBody;
    }

    /**
     * add capture id to paypal orders
     *
     * @param string $captureId
     * @param string $sellerId
     * @param string $orderId
     * @return void
     */
    public function updateSellerData($captureId, $sellerId, $orderId)
    {
        $collection = $this->paypalOrders->create()->getCollection()
        ->addFieldToFilter('paypal_order_id', $orderId)
        ->addFieldToFilter('seller_id', $sellerId)
        ->getFirstItem();

        $collection->setCaptureId($captureId);
        $collection->setUpdatedAt($this->_date->gmtDate());
        $collection->save();
    }

    /**
     * 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
                    ]
            ).'rest/V1/mppaypal/webhook/',
            'event_types' => [
                ['name' => 'MERCHANT.ONBOARDING.COMPLETED'],
                ['name' => 'MERCHANT.PARTNER-CONSENT.REVOKED'],
                ['name' => 'CHECKOUT.ORDER.COMPLETED'],
                ['name' => 'PAYMENT.CAPTURE.COMPLETED'],
                ['name' => 'PAYMENT.CAPTURE.DENIED'],
                ['name' => 'PAYMENT.CAPTURE.REFUNDED'],
                ['name' => 'PAYMENT.REFERENCED-PAYOUT-ITEM.COMPLETED'],
                ['name' => 'PAYMENT.REFERENCED-PAYOUT-ITEM.FAILED']
            ]
        ];
        $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['transaction_data']['payments']['captures'][0];
        $sellerId = explode('sellerId_', $purchaseUnit['transaction_data']['reference_id'])[1];
        $payee = $this->helper->getSubmerchantId($sellerId);
        $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;
    }

    /**
     * find order in paypal
     *
     * @param string $orderId
     * @return array
     */
    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;
    }

    /**
     * release merchant payout
     *
     * @param string $captureId
     * @return array
     */
    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);
            return ['error' => 1, 'message' => $message];
        }
        if ($responseBody['processing_state']['status'] == 'SUCCESS') {
            return $responseBody;
        } else {
            $message = __('Sorry, but something went wrong');
            $this->helper->logDataInLogger($message);
            return ['error' => 1, 'message' => $message];
        }
    }
}
