<?php
/**
 *                       ######
 *                       ######
 * ############    ####( ######  #####. ######  ############   ############
 * #############  #####( ######  #####. ######  #############  #############
 *        ######  #####( ######  #####. ######  #####  ######  #####  ######
 * ###### ######  #####( ######  #####. ######  #####  #####   #####  ######
 * ###### ######  #####( ######  #####. ######  #####          #####  ######
 * #############  #############  #############  #############  #####  ######
 *  ############   ############  #############   ############  #####  ######
 *                                      ######
 *                               #############
 *                               ############
 *
 * Adyen Payment module (https://www.adyen.com/)
 *
 * Copyright (c) 2019 Adyen BV (https://www.adyen.com/)
 * See LICENSE.txt for license details.
 *
 * Author: Adyen <magento@adyen.com>
 */

namespace Adyen\Payment\Helper;

use Adyen\Payment\Observer\AdyenOneclickDataAssignObserver;
use Adyen\Util\Uuid;
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Vault\Model\Ui\VaultConfigProvider;

use Adyen\Payment\Observer\AdyenHppDataAssignObserver;
use Adyen\Payment\Observer\AdyenCcDataAssignObserver;
use Magento\Quote\Api\Data\PaymentInterface;

class Requests extends AbstractHelper
{
    /**
     * @var Data
     */
    private $adyenHelper;

    /**
     * @var \Adyen\Payment\Helper\Config
     */
    private $adyenConfig;

    /**
     * @var \Magento\Framework\UrlInterface
     */
    private $urlBuilder;

    /**
     * Requests constructor.
     *
     * @param Data $adyenHelper
     * @param Config $adyenConfig
     * @param \Magento\Framework\UrlInterface $urlBuilder
     */
    public function __construct(
        \Adyen\Payment\Helper\Data $adyenHelper,
        \Adyen\Payment\Helper\Config $adyenConfig,
        \Magento\Framework\UrlInterface $urlBuilder
    ) {
        $this->adyenHelper = $adyenHelper;
        $this->adyenConfig = $adyenConfig;
        $this->urlBuilder = $urlBuilder;
    }

    /**
     * @param $request
     * @param $paymentMethod
     * @param $storeId
     * @return mixed
     */
    public function buildMerchantAccountData($paymentMethod, $storeId, $request = [])
    {
        // Retrieve merchant account
        $merchantAccount = $this->adyenHelper->getAdyenMerchantAccount($paymentMethod, $storeId);

        // Assign merchant account to request object
        $request['merchantAccount'] = $merchantAccount;

        return $request;
    }

    /**
     * @param int $customerId
     * @param $billingAddress
     * @param $storeId
     * @param null $payment
     * @param null $additionalData
     * @return array
     * @param array $request
     */
    public function buildCustomerData(
        $billingAddress,
        $storeId,
        $customerId = 0,
        $payment = null,
        $additionalData = null,
        $request = []
    ) {
        if ($customerId > 0) {
            $request['shopperReference'] = $customerId;
        }
        elseif ($this->adyenHelper->isGuestTokenizationEnabled($storeId)){
            $uuid = Uuid::generateV4();
            $guestCustomerId =  $payment->getOrder()->getIncrementId() . $uuid;
            $request['shopperReference'] = $guestCustomerId;
        }

        $paymentMethod = '';
        if ($payment) {
            $paymentMethod = $payment->getAdditionalInformation(AdyenHppDataAssignObserver::BRAND_CODE);
        }

        // In case of virtual product and guest checkout there is a workaround to get the guest's email address
        if (!empty($additionalData['guestEmail'])) {
            if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethod) &&
                !$this->adyenHelper->isPaymentMethodAfterpayTouchMethod($paymentMethod)
            ) {
                $request['paymentMethod']['personalDetails']['shopperEmail'] = $additionalData['guestEmail'];
            } else {
                $request['shopperEmail'] = $additionalData['guestEmail'];
            }
        }

        if (!empty($billingAddress)) {
            // Openinvoice (klarna and afterpay BUT not afterpay touch) methods requires different request format
            if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethod) &&
                !$this->adyenHelper->isPaymentMethodAfterpayTouchMethod($paymentMethod)
            ) {
                if ($customerEmail = $billingAddress->getEmail()) {
                    $request['paymentMethod']['personalDetails']['shopperEmail'] = $customerEmail;
                }

                if ($customerTelephone = trim($billingAddress->getTelephone())) {
                    $request['paymentMethod']['personalDetails']['telephoneNumber'] = $customerTelephone;
                }

                if ($firstName = $billingAddress->getFirstname()) {
                    $request['paymentMethod']['personalDetails']['firstName'] = $firstName;
                }

                if ($lastName = $billingAddress->getLastname()) {
                    $request['paymentMethod']['personalDetails']['lastName'] = $lastName;
                }
            } else {
                if ($customerEmail = $billingAddress->getEmail()) {
                    $request['shopperEmail'] = $customerEmail;
                }

                if ($customerTelephone = trim($billingAddress->getTelephone())) {
                    $request['telephoneNumber'] = $customerTelephone;
                }

                if ($firstName = $billingAddress->getFirstname()) {
                    $request['shopperName']['firstName'] = $firstName;
                }

                if ($lastName = $billingAddress->getLastname()) {
                    $request['shopperName']['lastName'] = $lastName;
                }
            }

            if ($countryId = $billingAddress->getCountryId()) {
                $request['countryCode'] = $countryId;
            }

            $request['shopperLocale'] = $this->adyenHelper->getCurrentLocaleCode($storeId);
        }

        return $request;
    }

    /**
     * @param $request
     * @param $ipAddress
     * @return mixed
     */
    public function buildCustomerIpData($ipAddress, $request = [])
    {
        $request['shopperIP'] = $ipAddress;

        return $request;
    }

    /**
     * @param $request
     * @param $billingAddress
     * @param $shippingAddress
     * @return mixed
     */
    public function buildAddressData($billingAddress, $shippingAddress, $request = [])
    {
        if ($billingAddress) {
            // Billing address defaults
            $requestBillingDefaults = [
                "street" => "N/A",
                "postalCode" => '',
                "city" => "N/A",
                "houseNumberOrName" => '',
                "country" => "ZZ"
            ];

            // Save the defaults for later to compare if anything has changed
            $requestBilling = $requestBillingDefaults;

            $address = $this->getStreetStringFromAddress($billingAddress);

            if (!empty($address["name"])) {
                $requestBilling["street"] = $address["name"];
            }

            if (!empty($address["house_number"])) {
                $requestBilling["houseNumberOrName"] = $address["house_number"];
            }

            if (!empty($billingAddress->getPostcode())) {
                $requestBilling["postalCode"] = $billingAddress->getPostcode();
            }

            if (!empty($billingAddress->getCity())) {
                $requestBilling["city"] = $billingAddress->getCity();
            }

            if (!empty($billingAddress->getRegionCode())) {
                $requestBilling["stateOrProvince"] = $billingAddress->getRegionCode();
            }

            if (!empty($billingAddress->getCountryId())) {
                $requestBilling["country"] = $billingAddress->getCountryId();
            }

            // If nothing is changed which means delivery address is not filled
            if ($requestBilling !== $requestBillingDefaults) {
                $request['billingAddress'] = $requestBilling;
            }
        }

        if ($shippingAddress) {
            // Delivery address defaults
            $requestDeliveryDefaults = [
                "street" => "N/A",
                "postalCode" => '',
                "city" => "N/A",
                "houseNumberOrName" => '',
                "country" => "ZZ"
            ];

            // Save the defaults for later to compare if anything has changed
            $requestDelivery = $requestDeliveryDefaults;

            // Parse address into street and house number where possible
            $address = $this->getStreetStringFromAddress($shippingAddress);

            if (!empty($address['name'])) {
                $requestDelivery["street"] = $address["name"];
            }

            if (!empty($address["house_number"])) {
                $requestDelivery["houseNumberOrName"] = $address["house_number"];
            }

            if (!empty($shippingAddress->getPostcode())) {
                $requestDelivery["postalCode"] = $shippingAddress->getPostcode();
            }

            if (!empty($shippingAddress->getCity())) {
                $requestDelivery["city"] = $shippingAddress->getCity();
            }

            if (!empty($shippingAddress->getRegionCode())) {
                $requestDelivery["stateOrProvince"] = $shippingAddress->getRegionCode();
            }

            if (!empty($shippingAddress->getCountryId())) {
                $requestDelivery["country"] = $shippingAddress->getCountryId();
            }

            // If nothing is changed which means delivery address is not filled
            if ($requestDelivery !== $requestDeliveryDefaults) {
                $request['deliveryAddress'] = $requestDelivery;
            }
        }

        return $request;
    }

    /**
     * @param array $request
     * @param $amount
     * @param $currencyCode
     * @param $reference
     * @param $paymentMethod
     * @return array
     */
    public function buildPaymentData($amount, $currencyCode, $reference, $paymentMethod, $request = [])
    {
        $request['amount'] = [
            'currency' => $currencyCode,
            'value' => $this->adyenHelper->formatAmount($amount, $currencyCode)
        ];

        $request["reference"] = $reference;
        $request["fraudOffset"] = "0";

        return $request;
    }

    /**
     * @param array $request
     * @return array
     */
    public function buildBrowserData($request = [])
    {
        if (!empty($_SERVER['HTTP_USER_AGENT'])) {
            $request['browserInfo']['userAgent'] = $_SERVER['HTTP_USER_AGENT'];
        }

        if (!empty($_SERVER['HTTP_ACCEPT'])) {
            $request['browserInfo']['acceptHeader'] = $_SERVER['HTTP_ACCEPT'];
        }

        return $request;
    }

    /**
     * @param array $request
     * @param $additionalData
     * @param $storeId
     * @return array
     */
    public function buildThreeDS2Data($additionalData, $storeId, $request = [])
    {
        if ($this->adyenHelper->isCreditCardThreeDS2Enabled($storeId)) {
            $request['additionalData']['allow3DS2'] = true;
            $request['origin'] = $this->adyenHelper->getOrigin($storeId);
            $request['channel'] = 'web';
            $request['browserInfo']['screenWidth'] = $additionalData[AdyenCcDataAssignObserver::SCREEN_WIDTH];
            $request['browserInfo']['screenHeight'] = $additionalData[AdyenCcDataAssignObserver::SCREEN_HEIGHT];
            $request['browserInfo']['colorDepth'] = $additionalData[AdyenCcDataAssignObserver::SCREEN_COLOR_DEPTH];
            $request['browserInfo']['timeZoneOffset'] = $additionalData[AdyenCcDataAssignObserver::TIMEZONE_OFFSET];
            $request['browserInfo']['language'] = $additionalData[AdyenCcDataAssignObserver::LANGUAGE];

            if ($javaEnabled = $additionalData[AdyenCcDataAssignObserver::JAVA_ENABLED]) {
                $request['browserInfo']['javaEnabled'] = $javaEnabled;
            } else {
                $request['browserInfo']['javaEnabled'] = false;
            }
        } else {
            $request['additionalData']['allow3DS2'] = false;
            $request['origin'] = $this->adyenHelper->getOrigin($storeId);
            $request['channel'] = 'web';
        }

        return $request;
    }

    /**
     * @param array $request
     * @return array
     */
    public function buildRedirectData($storeId, $request = [])
    {
        $request['redirectFromIssuerMethod'] = 'GET';
        $request['redirectToIssuerMethod'] = 'POST';
        $request['returnUrl'] = $this->urlBuilder->getUrl('adyen/process/redirect');
        return $request;
    }

    /**
     * @param $request
     * @param $areaCode
     * @param $storeId
     * @param $payment
     */
    public function buildRecurringData($areaCode, int $storeId, $additionalData, $customerId, $request = [])
    {
        $isGuestUser = true;
        if ($customerId > 0) {
            $isGuestUser = false;
        }
        //active
        if ( $this->adyenConfig->isStoreAlternativePaymentMethodEnabled($storeId)) {
            $request['storePaymentMethod'] = true;
        }
        // If the vault feature is on this logic is handled in the VaultDataBuilder
        if (!$this->adyenHelper->isCreditCardVaultEnabled()) {
            if ($areaCode !== \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE) {
                $storeId = null;
            }

            $enableOneclick = $this->adyenHelper->getAdyenAbstractConfigData('enable_oneclick', $storeId);
            $enableRecurring = $this->adyenHelper->getAdyenAbstractConfigData('enable_recurring', $storeId);

            $request['enableOneClick'] = $enableOneclick && !$isGuestUser;
            $request['enableRecurring'] = (bool)$enableRecurring;

            // value can be 0,1 or true
            if (!empty($additionalData[AdyenCcDataAssignObserver::STORE_CC]) || ($isGuestUser && $this->adyenHelper->isGuestTokenizationEnabled($storeId))) {
                $request['paymentMethod']['storeDetails'] = true;
            }
        }

        return $request;
    }

    /**
     * @param $request
     * @param $payment
     * @param $storeIdbuildCCData
     * @return mixed
     */
    public function buildCCData($payload, $storeId, $areaCode, $request = [])
    {
        // If ccType is set use this. For bcmc you need bcmc otherwise it will fail

        if (!empty($payload['method']) && $payload['method'] == 'adyen_oneclick' &&
            !empty($payload[PaymentInterface::KEY_ADDITIONAL_DATA]['variant'])
        ) {
            $request['paymentMethod']['type'] = $payload[PaymentInterface::KEY_ADDITIONAL_DATA]['variant'];
        } else {
            $request['paymentMethod']['type'] = 'scheme';
        }

        if (!empty($payload[PaymentInterface::KEY_ADDITIONAL_DATA]
            [AdyenCcDataAssignObserver::ENCRYPTED_CREDIT_CARD_NUMBER]) &&
            $cardNumber = $payload[PaymentInterface::KEY_ADDITIONAL_DATA]
            [AdyenCcDataAssignObserver::ENCRYPTED_CREDIT_CARD_NUMBER]) {
            $request['paymentMethod']['encryptedCardNumber'] = $cardNumber;
        }

        if (!empty($payload[PaymentInterface::KEY_ADDITIONAL_DATA]
            [AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_MONTH]) &&
            $expiryMonth = $payload[PaymentInterface::KEY_ADDITIONAL_DATA]
            [AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_MONTH]) {
            $request['paymentMethod']['encryptedExpiryMonth'] = $expiryMonth;
        }

        if (!empty($payload[PaymentInterface::KEY_ADDITIONAL_DATA]
            [AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_YEAR]) &&
            $expiryYear = $payload[PaymentInterface::KEY_ADDITIONAL_DATA]
            [AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_YEAR]) {
            $request['paymentMethod']['encryptedExpiryYear'] = $expiryYear;
        }

        if (!empty($payload[PaymentInterface::KEY_ADDITIONAL_DATA]
            [AdyenCcDataAssignObserver::HOLDER_NAME]) && $holderName =
                $payload[PaymentInterface::KEY_ADDITIONAL_DATA][AdyenCcDataAssignObserver::HOLDER_NAME]) {
            $request['paymentMethod']['holderName'] = $holderName;
        }

        if (!empty($payload[PaymentInterface::KEY_ADDITIONAL_DATA]
            [AdyenCcDataAssignObserver::ENCRYPTED_SECURITY_CODE]) &&
            $securityCode = $payload[PaymentInterface::KEY_ADDITIONAL_DATA]
            [AdyenCcDataAssignObserver::ENCRYPTED_SECURITY_CODE]) {
            $request['paymentMethod']['encryptedSecurityCode'] = $securityCode;
        }

        if (!empty($payload[PaymentInterface::KEY_ADDITIONAL_DATA]
            [AdyenOneclickDataAssignObserver::RECURRING_DETAIL_REFERENCE]) &&
            $recurringDetailReference = $payload[PaymentInterface::KEY_ADDITIONAL_DATA]
            [AdyenOneclickDataAssignObserver::RECURRING_DETAIL_REFERENCE]
        ) {
            $request['paymentMethod']['recurringDetailReference'] = $recurringDetailReference;
        }

        // set customerInteraction
        $recurringContractType = $this->adyenHelper->getAdyenOneclickConfigData('recurring_payment_type');
        if (!empty($payload['method']) && $payload['method'] == 'adyen_oneclick'
            && $recurringContractType == \Adyen\Payment\Model\RecurringType::RECURRING) {
            $request['shopperInteraction'] = "ContAuth";
        } else {
            $request['shopperInteraction'] = "Ecommerce";
        }

        /**
         * if MOTO for backend is enabled use MOTO as shopper interaction type
         */
        $enableMoto = $this->adyenHelper->getAdyenCcConfigDataFlag('enable_moto', $storeId);
        if ($areaCode === \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE &&
            $enableMoto
        ) {
            $request['shopperInteraction'] = "Moto";
        }

        // if installments is set add it into the request
        if (!empty($payload[PaymentInterface::KEY_ADDITIONAL_DATA]
        [AdyenCcDataAssignObserver::NUMBER_OF_INSTALLMENTS])) {
            if (($numberOfInstallment = $payload[PaymentInterface::KEY_ADDITIONAL_DATA]
                [AdyenCcDataAssignObserver::NUMBER_OF_INSTALLMENTS]) > 0) {
                $request['installments']['value'] = $numberOfInstallment;
            }
        }

        return $request;
    }


    /**
     * @param $request
     * @param $additionalInformation
     * @return mixed
     */
    public function buildVaultData($payload, $request = [])
    {
        if ($this->adyenHelper->isCreditCardVaultEnabled()) {
            if (!empty($payload[PaymentInterface::KEY_ADDITIONAL_DATA][VaultConfigProvider::IS_ACTIVE_CODE]) &&
                $payload[PaymentInterface::KEY_ADDITIONAL_DATA][VaultConfigProvider::IS_ACTIVE_CODE] === true ||
                !empty($payload[VaultConfigProvider::IS_ACTIVE_CODE]) &&
                $payload[VaultConfigProvider::IS_ACTIVE_CODE] === true
            ) {
                // store it only as oneclick otherwise we store oneclick tokens (maestro+bcmc) that will fail
                $request['enableRecurring'] = true;
            } else {
                // explicity turn this off as merchants have recurring on by default
                $request['enableRecurring'] = false;
            }
        }

        return $request;
    }

    /**
     * The billing address retrieved from the Quote and the one retrieved from the Order has some differences
     * Therefore we need to check if the getStreetFull function exists and use that if yes, otherwise use the more
     * commont getStreetLine1
     *
     * @param $billingAddress
     * @return array
     */
    private function getStreetStringFromAddress($address)
    {
        if (method_exists($address, 'getStreetFull')) {
            // Parse address into street and house number where possible
            $address = $this->adyenHelper->getStreetFromString($address->getStreetFull());
        } else {
            $address = $this->adyenHelper->getStreetFromString(
                implode(
                    ' ',
                    [
                        $address->getStreetLine1(),
                        $address->getStreetLine2(),
                        $address->getStreetLine3(),
                        $address->getStreetLine4()
                    ]
                )
            );
        }

        return $address;
    }
}