<?php
/**
 * 2007-2021 PrestaShop
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License (AFL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/afl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@prestashop.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
 * versions in the future. If you wish to customize PrestaShop for your
 * needs please refer to http://www.prestashop.com for more information.
 *
 * @author    PrestaShop SA <contact@prestashop.com>
 * @copyright 2007-2021 PrestaShop SA
 * @license   http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
 *  International Registered Trademark & Property of PrestaShop SA
 */

// @codingStandardsIgnoreStart
/* @noinspection AutoloadingIssuesInspection */
/* @noinspection PhpMultipleClassDeclarationsInspection */
/* @noinspection PhpIllegalPsrClassPathInspection */
/* @noinspection PhpDeprecationInspection */
/* @noinspection ReturnTypeCanBeDeclaredInspection */
// @codingStandardsIgnoreEnd

use ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs;
use Prestashop\Module\AddonPayments\AddonPaymentsConfig;
use Prestashop\Module\AddonPayments\Helpers\SecurityTrait;
use Prestashop\Module\AddonPayments\Operations\Authorize;
use Prestashop\Module\AddonPayments\Operations\DisableAccount;
use Prestashop\Module\AddonPayments\Services\ApiFactory;

/**
 * Handles user payment method registration.
 *
 * This controller is invoked when a user submits a payment method (aka payment solution account)
 * through the EPGJs rendered cashier.
 */
class AddonpaymentsVaultModuleFrontController extends ModuleFrontController
{
    use SecurityTrait;

    /**
     * @var \addonpayments
     */
    public $module;

    public $auth = true;

    public $ssl = _PS_USE_SSL_;

    /**
     * @var string
     */
    private string $token;

    public function __construct()
    {
        parent::__construct();
         $this->registerTranslations();
    }

     public function registerTranslations() {
         $this->module->l('Sorry, you have reached the registration limit for payment methods.');
         $this->module->l('The payment method could not be properly deleted. Please contact us and we will help you.');
     }

    public function initContent()
    {
        if ('POST' === $_SERVER['REQUEST_METHOD'] && !addonpayments::is17()) {
            if (null === Tools::getValue('action', null)) {
                $this->throwError();
            }

            $action = 'displayAjax' . ucfirst(Tools::getValue('action'));

            if (!method_exists($this, $action)) {
                $this->throwError();
            }

            $this->{$action}();
        }

        // PS 1.6 hide columns.
        $this->display_column_left = false;
        $this->display_column_right = false;
        parent::initContent();

        if (addonpayments::is17() && (true === $this->ajax || $this->isXmlHttpRequest())) {
            return;
        }

        $customer = $this->context->customer;

        if (!$customer->isLogged()) {
            Tools::redirect('/');
        }

        $payment_methods = $this->getRegisteredPaymentMethods();

        $this->token = (new Authorize())(
            $customer->id,
            $this->context->currency->iso_code,
            $this->context->country->iso_code,
            false
        );

        if (null === $this->token) {
            $this->warning[] = $this->module->l(
                'Sorry, seems due an unexpected error you cannot add more payment methods right now.'
            );
            $this->context->smarty->assign(
                [
                    'token' => $this->token,
                    'payment_methods' => $payment_methods,
                ]
            );
            $this->template = $this->module->getLocalPath() . 'views/templates/front/vault.tpl';

            return;
        }

        $vars = [
            'payment_methods' => $payment_methods,
            'instance_id' => sha1($customer->id . $customer->secure_key),
            'customer' => $customer->id,
            'token' => $this->token,
            'valid_until' => (time() * 1000) + ApiFactory::getTokenValidityThreshold(),
            'mep_base_url' => EPGJs::getEndpoints(ApiFactory::isLive())['base'],
            'register_callback' => $this->context->link->getModuleLink($this->module->name, 'vault'),
        ];
        $this->context->smarty->assign($vars);

        $this->template = $this->module->getLocalPath() . 'views/templates/front/vault.tpl';

        if (!addonpayments::is17()) {
            $this->template = $this->module->getLocalPath() . 'views/templates/front/vault--legacy.tpl';
        }
    }

    /**
     * Throws a bad request error.
     */
    private function throwError(): void
    {
        $this->ajaxDie(
            json_encode(
                [
                    'status' => 'error',
                    'message' => $this->module->l('Bad request'),
                ]
            )
        );
    }

    protected function getRegisteredPaymentMethods(): array
    {
        $tokens = CardToken::getByCustomer($this->context->customer->id);
        if (empty($tokens)) {
            return [];
        }

        foreach ($tokens as $key => $token) {
            unset($token['token'], $token['rawData']);

            $tokens[$key]['createdAt'] = Tools::displayDate($token['createdAt'], null);
            $tokens[$key]['lastUsed'] = Tools::displayDate($token['lastUsed'], null);
            $tokens[$key]['url'] = $this->context->link->getModuleLink(
                $this->module->name,
                'vault',
                [
                    'reference' => $token['accountId'],
                ]
            );
        }

        return $tokens;
    }

    public function setMedia()
    {
        parent::setMedia();
        $this->module->registerJavascript(
            EPGJs::getEndpoints(ApiFactory::isLive())['render'],
            'epgjs',
            ['priority' => 200, 'server' => 'remote']
        );
        $this->module->registerJavascript(
            "modules/{$this->module->name}/views/js/vault.js",
            __CLASS__ . '_js',
            ['priority' => 201]
        );
        $this->module->registerStylesheet(
            "modules/{$this->module->name}/views/css/front.css",
            "{$this->module->name}_css",
            ['priority' => 201]
        );

        $customer = $this->context->customer;
        $instance = sha1($customer->id . $customer->secure_key);
        $langcode = $this->context->language->iso_code;
        Media::addJsDef(
            [
                $this->module->name => [
                  'instance'     => $instance,
                  'langcode'     => $langcode,
                  'style'        => Configuration::get(AddonPaymentsConfig::EPG_STYLE),
                  'translations' => [
                        'unknownError'       => $this->module->l(
                            'An unexpected error occurred and your action cannot be performed. Please try reloading the page and if the problem persists contact us.'
                        ),
                        'reachedPaysolLimit' => $this->module->l(
                            'Sorry, you have reached the registration limit for payment methods.'
                        ),
                    ],
                  'i18n'         => [
                        $langcode => ApiFactory::getI18n($langcode),
                    ],
                ],
            ]
        );
    }

    public function postProcess()
    {
        if (true === $this->ajax || $this->isXmlHttpRequest()) {
            return true;
        }

        if ('POST' !== $_SERVER['REQUEST_METHOD'] && false === Tools::getValue('reference')) {
            return false;
        }

        if ('POST' !== $_SERVER['REQUEST_METHOD']) {
            return $this->deleteRegisteredAccount(Tools::getValue('reference'));
        }

        try {
            if (false === (bool) $this->module->active
                || false === Validate::isLoadedObject($this->context->customer)
            ) {
                throw new RuntimeException('The context is not valid');
            }

            $registerResponse = Tools::getValue('registerResponse');
            $registerResponse = json_decode($registerResponse, true);

            if (empty($registerResponse) || JSON_ERROR_NONE !== json_last_error()) {
                throw new InvalidArgumentException('The request contains an invalid payload');
            }

            if ('registered' !== $registerResponse['action']) {
                return true;
            }

            if ($this->context->customer->id !== (int) $registerResponse['customer']) {
                throw new InvalidArgumentException('The customer does not match');
            }

            $attempt = $this->addCardRegistrationAttempt($registerResponse);
            $token = $this->addCardToVault($registerResponse);

            return $attempt && $token;
        } catch (Exception $e) {
            PrestaShopLogger::addLog($e->getMessage(), 3);
        } finally {
            Tools::redirect($this->context->link->getModuleLink($this->module->name, 'vault'));
        }

        return true;
    }

    protected function deleteRegisteredAccount(string $accountId): bool
    {
        try {
            $token = (new Authorize())(
                $this->context->customer->id,
                $this->context->currency->iso_code,
                $this->context->country->iso_code,
                false
            );

            if (null === $token) {
                throw new RuntimeException('Could not get a valid authorization token for account disable operation');
            }

            (new DisableAccount())($token, $accountId);

            if (!CardToken::deleteByAccountId($accountId)) {
                throw new RuntimeException('Could not delete account from database');
            }

            Tools::redirect($this->context->link->getModuleLink($this->module->name, 'vault'));

            return true;
        } catch (Exception $e) {
            $this->errors[] = $this->module->l(
                'The payment method could not be properly deleted. Please contact us and we will help you.'
            );
            PrestaShopLogger::addLog('Cannot delete account ' . $accountId . ': ' . $e->getMessage(), 3);

            return false;
        }
    }

    protected function addCardRegistrationAttempt(array $data): bool
    {
        $attempt = new CardAttempts();
        $attempt->customer_id = $this->context->customer->id;
        $attempt->rawData = pSQL($this->encrypt(json_encode($data),
          self::getEncryptionKey()
        ));
        $attempt->date = date('Y-m-d H:i:s');

        return $attempt->save();
    }

    protected function addCardToVault(array $data): bool
    {
        $token = new CardToken();
        $token->customer_id = (int) $data['customer'];
        $token->accountId = pSQL($data['accountId']);
        $token->paymentMethod = pSQL($data['paymentMethod']);
        $token->createdAt = date('Y-m-d H:i:s');
        $values = array_column($data['values'], 'value', 'name');

        foreach ($values as $key => $value) {
            switch (true) {
                case 'cardType' === $key:
                    $token->cardType = pSQL($value);
                    break;
                case 'cardHolderName' === $key:
                case 'chName' === $key:
                    $token->name = pSQL($value);
                    break;
                case 'maskedCardNumber' === $key:
                case 'cardNumber' === $key && !array_key_exists('maskedCardNumber', $values):
                    $token->cardNumber = $value;
                    break;
                case 'cardNumber' === $key && array_key_exists('maskedCardNumber', $values):
                case 'cardNumberToken' === $key:
                    $token->cardToken = pSQL($this->encrypt($value,
                      self::getEncryptionKey()
                    ));
                    break;
                default:
            }
        }
        $token->rawData = pSQL($this->encrypt(json_encode($data),
          self::getEncryptionKey()
        ));
        $tokenNumber = $token->cardToken;
        $tokenId = $this->loadTokenIfExists($tokenNumber);

        if ($tokenId) {
            $token->id_token = $tokenId;
        }

        return $token->save();
    }

    protected function loadTokenIfExists($tokenNumber)
    {
        $dbPrefix = _DB_PREFIX_;

        return Db::getInstance()->getValue(
            "SELECT id_token FROM {$dbPrefix}addonpayments_card_token WHERE cardToken = '$tokenNumber'"
        );
    }

    public function getTemplateVarPage()
    {
        $page = parent::getTemplateVarPage();
        $page['body_classes']['page-customer-account'] = true;

        return $page;
    }

    public function displayAjaxCheckoutRegister(): void
    {
        if (false === (bool) $this->module->active
            || false === Validate::isLoadedObject($this->context->customer)
        ) {
            $this->ajaxDie(
                json_encode(
                    [
                        'error' => [
                            'message' => $this->module->l('The context is not valid'),
                        ],
                    ]
                )
            );
        }

        $payload = file_get_contents('php://input');
        $payload = json_decode($payload, true);

        if (empty($payload) || JSON_ERROR_NONE !== json_last_error()) {
            $this->ajaxDie(
                json_encode(
                    [
                        'error' => [
                            'message' => $this->module->l('The request contains an invalid payload'),
                        ],
                    ]
                )
            );
        }

        if (ApiFactory::getCustomerIdFromContext($this->context) !== $payload['customerId']) {
            $this->ajaxDie(
                json_encode(
                    [
                        'error' => [
                            'message' => $this->module->l('The customer does not match'),
                        ],
                    ]
                )
            );
        }

        $payload['customer'] = $this->context->customer->id;
        $success = $this->addCardRegistrationAttempt($payload) && $this->addCardToVault($payload);

        $this->ajaxDie(json_encode(['success' => $success]));
    }
}
