<?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\Gateway\Response;

use Adyen\Payment\Helper\Data;
use Adyen\Payment\Logger\AdyenLogger;
use Magento\Vault\Api\Data\PaymentTokenFactoryInterface;
use Magento\Payment\Gateway\Response\HandlerInterface;
use Magento\Payment\Model\InfoInterface;
use Magento\Vault\Model\PaymentTokenManagement;
use Magento\Payment\Gateway\Data\PaymentDataObject;
use Magento\Vault\Api\PaymentTokenRepositoryInterface;

class VaultDetailsHandler implements HandlerInterface
{

    const RECURRING_DETAIL_REFERENCE = 'recurring.recurringDetailReference';
    const CARD_SUMMARY = 'cardSummary';
    const EXPIRY_DATE = 'expiryDate';
    const PAYMENT_METHOD = 'paymentMethod';
    const ADDITIONAL_DATA_ERRORS = array(
        self::RECURRING_DETAIL_REFERENCE => 'Missing Token in Result please enable in ' .
            'Settings -> API URLs and Response menu in the Adyen Customer Area Recurring details setting',
        self::CARD_SUMMARY => 'Missing cardSummary in Result please login to the adyen portal ' .
            'and go to Settings -> API URLs and Response and enable the Card summary property',
        self::EXPIRY_DATE => 'Missing expiryDate in Result please login to the adyen portal and go to ' .
            'Settings -> API URLs and Response and enable the Expiry date property',
        self::PAYMENT_METHOD => 'Missing paymentMethod in Result please login to the adyen portal and go to ' .
            'Settings -> API URLs and Response and enable the Variant property'
    );

    /**
     * @var PaymentTokenFactoryInterface
     */
    protected $paymentTokenFactory;

    /**
     * @var AdyenLogger
     */
    private $adyenLogger;

    /**
     * @var Data
     */
    private $adyenHelper;

    /**
     * @var PaymentTokenManagement
     */
    private $paymentTokenManagement;

    /**
     * @var
     */
    private $paymentTokenRepository;

    /**
     * VaultDetailsHandler constructor.
     *
     * @param PaymentTokenFactoryInterface $paymentTokenFactory
     * @param AdyenLogger $adyenLogger
     * @param Data $adyenHelper
     * @param PaymentTokenManagement $paymentTokenManagement
     * @param PaymentTokenRepositoryInterface $paymentTokenRepository
     */
    public function __construct(
        PaymentTokenFactoryInterface $paymentTokenFactory,
        AdyenLogger $adyenLogger,
        Data $adyenHelper,
        PaymentTokenManagement $paymentTokenManagement,
        PaymentTokenRepositoryInterface $paymentTokenRepository
    ) {
        $this->adyenLogger = $adyenLogger;
        $this->adyenHelper = $adyenHelper;
        $this->paymentTokenFactory = $paymentTokenFactory;
        $this->paymentTokenManagement = $paymentTokenManagement;
        $this->paymentTokenRepository = $paymentTokenRepository;
    }

    /**
     * @inheritdoc
     */
    public function handle(array $handlingSubject, array $response)
    {
        /** @var PaymentDataObject $orderPayment */
        $orderPayment = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($handlingSubject);

        $payment = $orderPayment->getPayment();

        if ($this->adyenHelper->isCreditCardVaultEnabled($payment->getOrder()->getStoreId())) {
            // add vault payment token entity to extension attributes
            $paymentToken = $this->getVaultPaymentToken($response, $payment);

            if (null !== $paymentToken) {
                $extensionAttributes = $this->getExtensionAttributes($payment);
                $extensionAttributes->setVaultPaymentToken($paymentToken);
            } else {
                $this->adyenLogger->error(
                    sprintf('Failure trying to save credit card token in vault for order %s',
                        $payment->getOrder()->getIncrementId())
                );
            }
        }
    }

    /**
     * Get vault payment token entity
     *
     * @param array $response
     * @param $payment
     * @return PaymentTokenInterface|null
     */
    private function getVaultPaymentToken(array $response, $payment)
    {

        if (empty($response['additionalData'])) {
            return null;
        }

        $additionalData = $response['additionalData'];

        $paymentToken = null;

        foreach (self::ADDITIONAL_DATA_ERRORS as $key => $errorMsg) {
            if (empty($additionalData[$key])) {
                $this->adyenLogger->error($errorMsg);
                return null;
            }
        }

        try {

            // Check if paymentToken exists already
            $paymentToken = $this->paymentTokenManagement->getByGatewayToken($additionalData[self::RECURRING_DETAIL_REFERENCE],
                $payment->getMethodInstance()->getCode(), $payment->getOrder()->getCustomerId());

            $paymentTokenSaveRequired = false;

            // In case the payment token does not exist, create it based on the additionalData
            if (is_null($paymentToken)) {
                /** @var PaymentTokenInterface $paymentToken */
                $paymentToken = $this->paymentTokenFactory->create(
                    PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD
                );

                $paymentToken->setGatewayToken($additionalData[self::RECURRING_DETAIL_REFERENCE]);

                if (strpos($additionalData[self::PAYMENT_METHOD], "paywithgoogle") !== false
                    && !empty($additionalData['paymentMethodVariant'])) {
                    $additionalData[self::PAYMENT_METHOD] = $additionalData['paymentMethodVariant'];
                    $paymentToken->setIsVisible(false);
                }
            } else {
                $paymentTokenSaveRequired = true;
            }

            $paymentToken->setExpiresAt($this->getExpirationDate($additionalData[self::EXPIRY_DATE]));

            $details = [
                'type' => $additionalData[self::PAYMENT_METHOD],
                'maskedCC' => $additionalData[self::CARD_SUMMARY],
                'expirationDate' => $additionalData[self::EXPIRY_DATE]
            ];

            $paymentToken->setTokenDetails(json_encode($details));

            // If the token is updated, it needs to be saved to keep the changes
            if ($paymentTokenSaveRequired) {
                $this->paymentTokenRepository->save($paymentToken);
            }
        } catch (\Exception $e) {
            $this->adyenLogger->error(print_r($e, true));
        }

        return $paymentToken;
    }

    /**
     * @param $expirationDate
     * @return string
     * @throws \Exception
     */
    private function getExpirationDate($expirationDate)
    {
        $expirationDate = explode('/', $expirationDate);

        //add leading zero to month
        $month = sprintf('%02d', $expirationDate[0]);

        $expDate = new \DateTime(
            $expirationDate[1]
            . '-'
            . $month
            . '-'
            . '01'
            . ' '
            . '00:00:00',
            new \DateTimeZone('UTC')
        );

        // add one month
        $expDate->add(new \DateInterval('P1M'));
        return $expDate->format('Y-m-d 00:00:00');
    }

    /**
     * Get payment extension attributes
     * @param InfoInterface $payment
     * @return OrderPaymentExtensionInterface
     */
    private function getExtensionAttributes(InfoInterface $payment)
    {
        $extensionAttributes = $payment->getExtensionAttributes();
        if (null === $extensionAttributes) {
            $extensionAttributes = $this->paymentExtensionFactory->create();
            $payment->setExtensionAttributes($extensionAttributes);
        }
        return $extensionAttributes;
    }
}