<?php

/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Sts\SmartRoute\Model;

use Magento\Quote\Api\Data\CartInterface;
use Magento\Payment\Model\Method\AbstractMethod;
use Magento\Sales\Model\Order;
use Magento\Store\Model\ScopeInterface;

class PaymentMethod extends AbstractMethod {

    const CODE = 'smartroute';
    const CURRENCIES_ISO = [
        'USD' => 840,
        'EUR' => 978,
        'GBP' => 826,
        'JOD' => 400,
        'AED' => 784,
        'SAR' => 682,
            //TODO: complete other currencies
    ];
    //const REQUEST_TYPE_CREDIT = 'CREDIT';
    CONST THEME_ID = "1000000001";
    CONST VERSION = "1.0";

    protected $_code = self::CODE;
    protected $_isInitializeNeeded = true;
    protected $_formBlockType = 'Sts\SmartRoute\Block\Form';
    protected $_infoBlockType = 'Sts\SmartRoute\Block\Info';
    protected $_isGateway = true;
    protected $_canAuthorize = true;
    protected $_canCapture = true;
    protected $_canCapturePartial = false;
    protected $_canRefund = true;
    protected $_canRefundInvoicePartial = true;
    protected $_canVoid = false;
    protected $_canUseInternal = true;
    protected $_canUseCheckout = true;
    protected $_canUseForMultishipping = false;
    protected $_canSaveCc = false;
    protected $urlBuilder;
    protected $checkoutSession;
    protected $_orderFactory;
    protected $customerSession;
    protected $context;
    protected $orderSender;
    protected $logger;
    protected $_transactionBuilder;

    public function __construct(
    \Magento\Framework\Model\Context $context, \Magento\Framework\Registry $registry, \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory, \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory, \Magento\Customer\Model\Session $customerSession, \Magento\Payment\Helper\Data $paymentData, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Payment\Model\Method\Logger $logger, \Magento\Sales\Model\OrderFactory $orderFactory, \Magento\Sales\Model\Order\Payment\Transaction\Builder $transactionBuilder, \Magento\Framework\Url $urlBuilder, \Magento\Checkout\Model\Session $checkoutSession, \Magento\Sales\Model\Order\Email\Sender\OrderSender $orderSender, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = []) {
        $this->urlBuilder = $urlBuilder;
        $this->checkoutSession = $checkoutSession;
        $this->_orderFactory = $orderFactory;
        $this->customerSession = $customerSession;
        $this->context = $context;
        $this->_transactionBuilder = $transactionBuilder;
        $this->orderSender = $orderSender;
        parent::__construct($context, $registry, $extensionFactory, $customAttributeFactory, $paymentData, $scopeConfig, $logger, $resource, $resourceCollection, $data);
    }

    public function initialize($paymentAction, $stateObject) {
        //$payment = $this->getInfoInstance();
        //$order = $payment->getOrder();

        $state = $this->_scopeConfig->getValue('payment/smartroute/new_order_status');

        //$state = Mage_Sales_Model_Order::STATE_PENDING_PAYMENT;
        $stateObject->setState($state);
        $stateObject->setStatus('pending_payment');
        $stateObject->setIsNotified(false);
    }

    public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null) {
        return true;
    }

    public function getOrderPlaceRedirectUrl() {
        $communicationModel = $this->_scopeConfig->getValue('payment/smartroute/communication_model');

        switch ($communicationModel) {
            case Config\CommunicationModel::COMMUNICATION_MODEL_REDIRECT:
                $ret = $this->urlBuilder->getUrl('stssmartroute/payment/redirect', ['_secure' => true]);
                break;
            case Config\CommunicationModel::COMMUNICATION_MODEL_DIRECT_POST:
                $ret = $this->urlBuilder->getUrl('stssmartroute/payment/directPost', ['_secure' => true]);
                break;
            case Config\CommunicationModel::COMMUNICATION_MODEL_API:
                $ret = $this->urlBuilder->getUrl('stssmartroute/payment/apiPayment', ['_secure' => true]);
                break;
        }

        return $ret;
    }

    public function getTotalAmountForGateway(Order $order, $baseAmount = null) {
        if ($baseAmount === null) {
            $baseAmount = $order->getGrandTotal();
        }
        if ($order->getBaseCurrencyCode() == 'JOD') {
            $multiplier = 1000;
        } else {
            $multiplier = 100;
        }
        $amount = number_format($baseAmount * $multiplier, 0, '.', '');

        return $amount;
    }

    /**
     * Authorize payment abstract method
     *
     * @param \Magento\Framework\DataObject|InfoInterface $payment
     * @param float $amount
     * @return $this
     * @throws \Magento\Framework\Exception\LocalizedException
     * @api
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function authorize(\Magento\Payment\Model\InfoInterface $payment, $amount) {
        if (!$this->canAuthorize()) {
            throw new \Magento\Framework\Exception\LocalizedException(__('The authorize action is not available.'));
        }
        return $this;
    }

    /**
     * Capture payment abstract method
     *
     * @param \Magento\Framework\DataObject|InfoInterface $payment
     * @param float $amount
     * @return $this
     * @throws \Magento\Framework\Exception\LocalizedException
     * @api
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount) {

        if (!$this->canCapture()) {
            throw new \Magento\Framework\Exception\LocalizedException(__('The capture action is not available.'));
        }

        return $this;
    }

    /**
     * Refund specified amount for payment
     *
     * @param \Magento\Framework\DataObject|InfoInterface $payment
     * @param float $amount
     * @return $this
     * @throws \Magento\Framework\Exception\LocalizedException
     * @api
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function refund(\Magento\Payment\Model\InfoInterface $payment, $amount) {

        $this->processRefund($payment, $amount);

        return $this;
    }

    /**
     * save transaction object for order and reference id/transaction id
     * @param Order $order
     * @param int $transactionId
     */
    public function savePaymentTransaction(Order $order, $transactionId) {

        $payment = $this->getOrCreatePayment($order);
        $payment->setLastTransId($transactionId);
        $payment->setTransactionId($transactionId);
        $formatedPrice = $order->getBaseCurrency()->formatTxt(
                $order->getGrandTotal()
        );
        $order->setLastTransId($transactionId);

        $message = __('Capture amount of %1.', $formatedPrice);
        //get the object of builder class
        $trans = $this->_transactionBuilder;
        $transaction = $trans->setPayment($payment)
                ->setOrder($order)
                ->setTransactionId($transactionId)
                ->setFailSafe(true)
                //build method creates the transaction and returns the object
                ->build(\Magento\Sales\Model\Order\Payment\Transaction::TYPE_CAPTURE);

        $payment->addTransactionCommentsToOrder(
                $transaction, $message
        );
        $payment->setParentTransactionId(null);
        $payment->setLastTransId($transactionId);
        $payment->save();
        $order->save();
    }

    /**
     * get or create payment associated with order
     * @param Order $order
     * @return type
     */
    private function getOrCreatePayment(Order $order) {
        $payment = $order->getPayment();
        if (!$payment) {
            //get payment object from order object
            $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
            /* @var $payment \Magento\Sales\Model\Order\Payment */
            $payment = $objectManager->get('\Magento\Sales\Api\Data\OrderPaymentInterface');
            $payment->setAmountAuthorized($order->getGrandTotal());
            $payment->setMethod(self::CODE);
            $payment->setParentId($order->getId());
            $payment->setOrder($order);
            $order->setPayment($payment);
           
        }
        $order->setStatus(Order::STATE_PENDING_PAYMENT);

        return $payment;
    }

    /**
     * mark order as completed upon successful transaction
     * @param Order $order
     */
    public function markOrderAsSuccessfull(Order $order, $transactionId = '') {
        if ($order->getState() != $this->_scopeConfig->getValue('payment/smartroute/complete_order_status', ScopeInterface::SCOPE_STORE)) {
            $order->setState($this->_scopeConfig->getValue('payment/smartroute/complete_order_status', ScopeInterface::SCOPE_STORE));
            $order->setTotalPaid($order->getGrandTotal());
            $order->setBaseTotalPaid($order->getBaseGrandTotal());
            $order->addStatusToHistory(Order::STATE_PROCESSING, __('Payment has been captured by Payment Gateway. Transaction id: %1', $transactionId));
            $order->addStatusToHistory($this->_scopeConfig->getValue('payment/smartroute/complete_order_status', ScopeInterface::SCOPE_STORE));

            $this->orderSender->send($order);
            $this->_createInvoice($order, $transactionId);
            $order->save();
        }
    }

    /**
     * get currency iso number from code
     * @param string $code
     * @return int
     */
    public function getCurrencyIsoNumberFromCode($code) {
        return isset(self::CURRENCIES_ISO[$code]) ?
        self::CURRENCIES_ISO[$code] :
        $code;
    }

    /**
     * process inquiry API for non-completed or canceled orders
     * @param Mage_Sales_Model_Order $order
     */
    public function processAPIInquiry(Order $order) {
        $inquiryParams = [];
        $secureHash = "";
        $this->getInquiryParametersAndHash($order, $secureHash, $inquiryParams);
        $result = $this->processInquiryRequest($inquiryParams);

        // Complete the Action get other parameters from result map and do your processes
        // please refer to The Integration Manual to See The List of The Received Parameters
        $status = $result["Response_StatusCode"];
        if ($status === '00000') {
            $this->markOrderAsSuccessfull($order, $inquiryParams['OriginalTransactionID']);
            $this->context->getLogger()->info("smartroute inquiry marking order#" . $order->getId() . ' as successfull');
        } else {
            $order->cancel();
            $order->addStatusHistoryComment(
                    __('canceled by Gateway. Transaction: %1', $inquiryParams['OriginalTransactionID']), Order::STATE_CANCELED);
            $order->save();
            $this->context->getLogger()->info("smartroute inquiry marking order#" . $order->getId() . ' as failed');
        }

        return [
            'status' => $status,
            'statusDescription' => $result['Response_StatusDescription'],
        ];
    }

    /**
     * creating invoice out of order
     * @param Order $order     
     * @throws \RuntimeException
     */
    protected function _createInvoice(Order $order, $transactionId) {
        if ($this->_scopeConfig->getValue('payment/smartroute/invoice') == 1) {
            $invoice = $order->prepareInvoice();
            if (!$invoice->getTotalQty()) {
                throw new \RuntimeException("Cannot create an invoice without products.");
            }
            $invoice->setTransactionId($transactionId);
            $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_ONLINE);
            $invoice->setState(\Magento\Sales\Model\Order\Invoice::STATE_PAID);
            $invoice->register();
            $order->addRelatedObject($invoice);
        } else {
            $this->context->getLogger()->info("smartroute cannot create invoice for order#" . $order->getId());
        }
    }

    protected function calculateRefundSecureHash(Order $order, $amount, &$secureHash, &$parameters) {
        $secretKey = $this->_scopeConfig->getValue('payment/smartroute/secret_key');
        $transactionId = (int) (microtime(true) * 1000);
        $totalAmount = $this->getTotalAmountForGateway($order, $amount);
        $currencyIso = $this->getCurrencyIsoNumberFromCode($order->getBaseCurrencyCode());

        // put the parameters in a array to have the parameters to have them sorted alphabetically via ksort.
        $parameters = [];
        // fill required parameters
        $parameters["MessageID"] = "4";
        $parameters["TransactionID"] = $transactionId;
        $parameters["OriginalTransactionID"] = $order->getPayment()->getLastTransId();
        $parameters["MerchantID"] = $this->_scopeConfig->getValue('payment/smartroute/merchant_id');
        $parameters["Amount"] = $totalAmount;
        $parameters["CurrencyISOCode"] = $currencyIso;
        $parameters["Version"] = "1.0";

        //sort parameters alphabatically
        ksort($parameters);
        //Create an Ordered String of The Parameters Map with Secret Key

        $orderedString = $secretKey;
        foreach ($parameters as $param) {
            $orderedString .= $param;
        }

        // Generate SecureHash with SHA256
        $secureHash = hash('sha256', $orderedString, false);
        return $secureHash;
    }

    protected function prepareRefundParameters($secureHash, $parameters) {
        $requestQueryArr = [
            "TransactionID" => $parameters['TransactionID'],
            "MerchantID" => $parameters['MerchantID'],
            "MessageID" => "4",
            "Amount" => $parameters['Amount'],
            "OriginalTransactionID" => $parameters['OriginalTransactionID'],
            "CurrencyISOCode" => $parameters['CurrencyISOCode'],
            "SecureHash" => $secureHash,
            "Version" => "1.0"
        ];

        return $requestQueryArr;
    }

    protected function processRefundRequest($requestQueryArr) {

        //log request for debugging purposes
        $this->context->getLogger()->info('smart route refund parameters: ' . json_encode($requestQueryArr));

        //generate query string
        $newRequestQuery = http_build_query($requestQueryArr);

        //Send the request
        $ch = curl_init($this->_scopeConfig->getValue('payment/smartroute/refund_url'));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        //write parameters
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $newRequestQuery);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 90);
        // Get the response
        $output = curl_exec($ch);
        curl_close($ch);

        $this->context->getLogger()->info('smart route refund response: ' . json_encode($output));
        // this string is formatted as a "Query String" - name=value&name2=value2.......
        $result = [];

        // To read the output string you might want to split it
        // on '&' to get pairs then on '=' to get name and value
        // and for a better and ease on verifying secure hash using parse_str
        parse_str($output, $result);
        //sort result alphabatically by key
        ksort($result);
        // Now that we have the array sorted, order it to generate secure hash and compare it with the received one
        return $result;
    }

    protected function calculateRefundResponseSecureHash($result) {
        unset($result['Response_SecureHash']);
        $secretKey = $this->_scopeConfig->getValue('payment/smartroute/secret_key');
        $responseOrderdString = $secretKey;
        foreach ($result as $result_v) {
            $responseOrderdString .= str_replace(' ', '+', $result_v);
        }

        // Generate SecureHash with SHA256
        $generatedsecureHash = hash('sha256', $responseOrderdString);

        return $generatedsecureHash;
    }

    /**
     * Refund the amount with transaction id
     *
     * @param \Magento\Framework\DataObject $payment
     * @param float $amount
     * @return $this
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    protected function processRefund($payment, $amount) {
        /* @var \Magento\Sales\Model\Order\Payment $payment */
        if ($amount <= 0.000) {
            throw new \Magento\Framework\Exception\LocalizedException(__('Invalid Refund Amount.'));
        }

        if (!$payment->getParentTransactionId()) {
            throw new \Magento\Framework\Exception\LocalizedException(__('Invalid transaction ID.'));
        }

        $parameters = [];
        $secureHash = "";
        $this->calculateRefundSecureHash($payment->getOrder(), $amount, $secureHash, $parameters);
        $refundParams = $this->prepareRefundParameters($secureHash, $parameters);
        $result = $this->processRefundRequest($refundParams);

        $generatedsecureHash = $this->calculateRefundResponseSecureHash($result);

        $status = (isset($result) AND isset($result["Response_StatusCode"])) ? $result["Response_StatusCode"] : NULL;

        // get the received secure hash from result map
        $receivedSecurehash = (isset($result) AND isset($result["Response_SecureHash"])) ? $result["Response_SecureHash"] : NULL;
        if (count($result)==0 || $receivedSecurehash != $generatedsecureHash) {

            $inquirySecureHash = null;
            $inquiryParams = [];
            $this->getInquiryParametersForRefund($parameters['TransactionID'], $inquirySecureHash, $inquiryParams);
            $resultInquiry = $this->processInquiryRequest($inquiryParams);

            $statusInquiry = ($resultInquiry AND isset($resultInquiry["Response_StatusCode"]))?$resultInquiry["Response_StatusCode"] : NULL;
            if ($statusInquiry === '00000') {
                $payment->setTransactionId($parameters['TransactionID']);
            } else {
                //If they are not equal then the response shall not be accepted
                throw new \Magento\Framework\Exception\LocalizedException(
                __('SmartRoute Refund Error: ' . $statusInquiry . ' Description: You can not complete this process')
                );
            }
        } else {

            // Complete the Action get other parameters from result map and do your processes
            // please refer to The Integration Manual to See The List of The Received Parameters            
            if ($status === '00000') {
                $payment->setTransactionId($parameters['TransactionID']);
            } else {
                throw new \Magento\Framework\Exception\LocalizedException(
                __('SmartRoute Refund Error: ' . $status . ' Description: ' . $result['Response_StatusDescription'])
                );
            }
        }
    }

    /**
     * getting inquiry parameters and hash
     * @param Mage_Sales_Model_Order $order     
     * @param string $secureHash
     * @param array $parameters     
     */
    protected function getInquiryParametersAndHash(Order $order, &$secureHash, &$parameters) {
        $secretKey = $this->_scopeConfig->getValue('payment/smartroute/secret_key');

        // put the parameters in a array to have the parameters to have them sorted alphabetically via ksort.
        $parameters = [];
        // fill required parameters
        $parameters["MessageID"] = "2";
        $parameters["MerchantID"] = $this->_scopeConfig->getValue('payment/smartroute/merchant_id');
        $parameters["OriginalTransactionID"] = $order->getPayment()->getLastTransId();
        $parameters["Version"] = self::VERSION;

        //sort parameters alphabatically
        ksort($parameters);
        //Create an Ordered String of The Parameters Map with Secret Key

        $orderedString = $secretKey;
        foreach ($parameters as $param) {
            $orderedString .= $param;
        }

        // Generate SecureHash with SHA256
        $secureHash = hash('sha256', $orderedString, false);
        $parameters['SecureHash'] = $secureHash;
    }

    /**
     * getting inquiry parameters and hash
     * @param string $transactionId
     * @param string $secureHash
     * @param array $parameters     
     */
    protected function getInquiryParametersForRefund($transactionId, &$secureHash, &$parameters) {
        $secretKey = $this->_scopeConfig->getValue('payment/smartroute/secret_key');

        // put the parameters in a array to have the parameters to have them sorted alphabetically via ksort.
        $parameters = [];
        // fill required parameters
        $parameters["MessageID"] = "2";
        $parameters["MerchantID"] = $this->_scopeConfig->getValue('payment/smartroute/merchant_id');
        $parameters["OriginalTransactionID"] = $transactionId;
        $parameters["Version"] = self::VERSION;

        //sort parameters alphabatically
        ksort($parameters);
        //Create an Ordered String of The Parameters Map with Secret Key

        $orderedString = $secretKey;
        foreach ($parameters as $param) {
            $orderedString .= $param;
        }

        // Generate SecureHash with SHA256
        $secureHash = hash('sha256', $orderedString, false);
        $parameters['SecureHash'] = $secureHash;
    }

    /**
     * send curl request for process inquiry api method
     * @param array $requestQueryArr
     * @return array
     */
    protected function processInquiryRequest($requestQueryArr) {

        //log request for debugging purposes        
        $this->context->getLogger()->info('smart route inquiry parameters: ' . json_encode($requestQueryArr));

        //generate query string
        $newRequestQuery = http_build_query($requestQueryArr);

        //Send the request        
        $ch = curl_init($this->_scopeConfig->getValue('payment/smartroute/inquiry_url'));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        //write parameters
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $newRequestQuery);

        // Get the response
        $output = curl_exec($ch);
        if (curl_errno($ch)) {
            throw new \Magento\Framework\Exception\LocalizedException(__('Error in inquiry curl error#' . curl_errno($ch) . ' ' . curl_error($ch)));
        }
        curl_close($ch);

        $this->context->getLogger()->info('smart route inquiry response: ' . $output);
        // this string is formatted as a "Query String" - name=value&name2=value2.......
        $result = [];

        // To read the output string you might want to split it
        // on '&' to get pairs then on '=' to get name and value
        // and for a better and ease on verifying secure hash using parse_str
        parse_str($output, $result);
        //sort result alphabatically by key
        ksort($result);
        // Now that we have the array sorted, order it to generate secure hash and compare it with the received one
        return $result;
    }

}
