<?php
/**
 * NOTICE OF LICENSE
 *
 * @author Mondial Relay <offrestart@mondialrelay.fr>
 * @copyright Copyright (c) Mondial Relay
 * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 */
use MondialrelayClasslib\Actions\ActionsHandler;

if (!defined('_PS_VERSION_')) {
    exit;
}

class MondialrelayAjaxCheckoutModuleFrontController extends ModuleFrontController
{
    /** @var string 'ok' or 'error' */
    public $status = '';

    /** @var array */
    public $warnings = [];

    /** @var array */
    public $informations = [];

    /** @var array */
    public $confirmations = [];

    /** @var array */
    public $errors = [];

    /** @var array */
    public $content = [];

    /**
     * @see AdminController
     * Shortcut to set up a json success payload
     *
     * @param string $message Success message
     */
    public function jsonConfirmation($message)
    {
        $this->json = true;
        $this->confirmations[] = $message;
        if ($this->status === '') {
            $this->status = 'ok';
        }
    }

    /**
     * @see AdminController
     * Shortcut to set up a json error payload
     *
     * @param string $message Error message
     */
    public function jsonError($message)
    {
        $this->json = true;
        $this->errors[] = $message;
        if ($this->status === '') {
            $this->status = 'error';
        }
    }

    protected function displayAjax()
    {
        header('Content-Type: application/json');
        echo json_encode([
            'status' => $this->status ? $this->status : 'ok',
            'warnings' => $this->warnings,
            'informations' => $this->informations,
            'confirmations' => $this->confirmations,
            'error' => $this->errors,
            'content' => $this->content,
        ]);
    }

    /**
     * @see FrontController::postProcess()
     */
    public function postProcess()
    {
        ob_start();
        parent::postProcess();
        if ($this->ajax) {
            $action = Tools::getValue('action');
            switch ($action) {
                case 'saveSelectedRelay':
                    if (!$this->isValidSaveSelectedRelayRequest()) {
                        return;
                    }
                    $this->saveSelectedRelay();
                    break;
                default:
                    exit;
            }
        }
    }

    /**
     * Ensures saveSelectedRelay can only be called from a valid checkout context.
     *
     * @return bool
     */
    protected function isValidSaveSelectedRelayRequest()
    {
        $requestMethod = isset($_SERVER['REQUEST_METHOD']) ? Tools::strtoupper($_SERVER['REQUEST_METHOD']) : '';
        if ($requestMethod !== 'POST') {
            $this->jsonError($this->module->l('Invalid request method.', 'ajaxCheckout'));
            return false;
        }

        $token = (string) Tools::getValue('token');
        $expectedToken = $this->getExpectedCheckoutAjaxCsrfToken();
        $fallbackToken = (string) Tools::getToken(false);

        $isTokenValid = $token
            && (
                ($expectedToken && $this->compareTokens($expectedToken, $token))
                || ($fallbackToken && $this->compareTokens($fallbackToken, $token))
            );

        if (!$isTokenValid) {
            $this->jsonError($this->module->l('Security token is invalid. Please refresh the page and try again.', 'ajaxCheckout'));
            return false;
        }

        return true;
    }

    /**
     * Retrieves checkout CSRF token expected for this cookie session.
     *
     * @return string
     */
    protected function getExpectedCheckoutAjaxCsrfToken()
    {
        $cookieKey = Mondialrelay::AJAX_CHECKOUT_CSRF_TOKEN_COOKIE;

        if (!isset($this->context->cookie->$cookieKey)) {
            return '';
        }

        return (string) $this->context->cookie->$cookieKey;
    }

    /**
     * Constant-time comparison when available.
     *
     * @param string $expectedToken
     * @param string $token
     *
     * @return bool
     */
    protected function compareTokens($expectedToken, $token)
    {
        if (function_exists('hash_equals')) {
            return hash_equals($expectedToken, $token);
        }

        return $expectedToken === $token;

    }

    /**
     * Saves the selected relay
     */
    protected function saveSelectedRelay()
    {
        if (
            !MondialRelayTools::checkDependencies()
            || (!Configuration::get(MondialRelay::HOME_DELIVERY) && !MondialRelayTools::checkWebserviceConfiguration())
            || (Configuration::get(MondialRelay::HOME_DELIVERY) && !MondialRelayTools::checkWebserviceConfigurationApi2())
        ) {
            $this->jsonError($this->module->l('This carrier has not been configured yet; please contact the merchant.', 'ajaxCheckout'));
            return false;
        }

        // If Mondial Relay isn't set up...
        if (!MondialRelayTools::checkDependencies() || !MondialRelayTools::checkWebserviceConfiguration()) {
            $this->jsonError($this->module->l('This carrier has not been configured yet; please contact the merchant.', 'ajaxCheckout'));
            return false;
        }

        $cart = $this->context->cart;
        $id_carrier = Tools::getValue('id_carrier', $cart->id_carrier);

        // If we're not using a Mondial Relay carrier...
        $carrierMethod = MondialrelayCarrierMethod::getFromNativeCarrierId($id_carrier);
        if (!Validate::isLoadedObject($carrierMethod)) {
            $this->jsonError($this->module->l('This carrier is not registered with Mondial Relay.', 'ajaxCheckout'));
            return false;
        }

        // If the carrier doesn't need a relay...
        if (!$carrierMethod->needsRelay()) {
            $this->jsonError($this->module->l('This carrier does not need a relay selection.', 'ajaxCheckout'));
            return false;
        }

        // If we're not selecting a relay...
        $newRelay = Tools::getValue('mondialrelay_selectedRelay');
        if (!$newRelay) {
            if ($carrierMethod->delivery_mode == "MED") {
                $this->jsonError($this->module->l('Please select a Point Relais®.', 'ajaxCheckout'));
            } elseif ($carrierMethod->delivery_mode == "24R") {
                $this->jsonError($this->module->l('Please select a Locker / Point Relais®.', 'ajaxCheckout'));
            } elseif ($carrierMethod->delivery_mode == "APM") {
                $this->jsonError($this->module->l('Please select a Locker.', 'ajaxCheckout'));
            }

            return false;
        }

        $newRelayInfos = Tools::getValue('mondialrelay_selectedRelay_infos');
        if (!$newRelayInfos) {
            if ($carrierMethod->delivery_mode == "MED") {
                $this->jsonError($this->module->l('Please select a Point Relais®.', 'ajaxCheckout'));
            } elseif ($carrierMethod->delivery_mode == "24R") {
                $this->jsonError($this->module->l('Please select a Locker / Point Relais®.', 'ajaxCheckout'));
            } elseif ($carrierMethod->delivery_mode == "APM") {
                $this->jsonError($this->module->l('Please select a Locker.', 'ajaxCheckout'));
            }
            return false;
        }

        list($country_iso, $relayNumber) = explode('-', $newRelay);
        if (!$country_iso || !$relayNumber) {
            if ($carrierMethod->delivery_mode == "MED") {
                $this->jsonError($this->module->l('Please select a Point Relais®.', 'ajaxCheckout'));
            } elseif ($carrierMethod->delivery_mode == "24R") {
                $this->jsonError($this->module->l('Please select a Locker / Point Relais®.', 'ajaxCheckout'));
            } elseif ($carrierMethod->delivery_mode == "APM") {
                $this->jsonError($this->module->l('Please select a Locker.', 'ajaxCheckout'));
            }
            return false;
        }

        // Limites de longueur alignées sur MondialrelaySelectedRelay::$definition['fields']
        $newRelayInfosFormatted = [
            "LgAdr1" => Tools::substr(strip_tags(isset($newRelayInfos['Nom'])      ? $newRelayInfos['Nom']      : ""), 0, 36),
            "LgAdr2" => Tools::substr(strip_tags(isset($newRelayInfos['Adresse4']) ? $newRelayInfos['Adresse4'] : ""), 0, 36),
            "LgAdr3" => Tools::substr(strip_tags(isset($newRelayInfos['Adresse1']) ? $newRelayInfos['Adresse1'] : ""), 0, 36),
            "LgAdr4" => Tools::substr(strip_tags(isset($newRelayInfos['Adresse2']) ? $newRelayInfos['Adresse2'] : ""), 0, 36),
            "Num"    => Tools::substr(preg_replace('/[^A-Z0-9]/i', '', isset($newRelayInfos['ID'])    ? $newRelayInfos['ID']    : ""), 0, 6),
            "CP"     => Tools::substr(preg_replace('/[^A-Z0-9\-]/i', '', isset($newRelayInfos['CP'])  ? $newRelayInfos['CP']   : ""), 0, 10),
            "Ville"  => Tools::substr(strip_tags(isset($newRelayInfos['Ville'])    ? $newRelayInfos['Ville']    : ""), 0, 32),
            "Pays"   => Tools::substr(preg_replace('/[^A-Z]/i', '', isset($newRelayInfos['Pays'])     ? $newRelayInfos['Pays'] : ""), 0, 2),
            "Nom"    => Tools::substr(strip_tags(isset($newRelayInfos['Nom'])      ? $newRelayInfos['Nom']      : ""), 0, 36),
        ];

        if ($newRelayInfosFormatted['Pays'] == 'PT') {
            $newRelayInfosFormatted['CP'] = str_replace(' ', '-', $newRelayInfosFormatted['CP']);
        }

        $newRelayInfosFormatted = (object)$newRelayInfosFormatted;

        // Create the handler
        $handler = new ActionsHandler();

        // Set input data
        $handler->setConveyor([
            'enseigne' => Configuration::get(Mondialrelay::WEBSERVICE_ENSEIGNE),
            'country_iso' => $country_iso,
            'relayNumber' => $relayNumber,
            'carrierMethod' => $carrierMethod,
            // We need to pass the whole cart, otherwise our modifications
            // may be overwritten if PS saves the cart during its process
            'cart' => $cart,
            'relayInfos' => $newRelayInfosFormatted,
        ]);
        $actions = ['setSelectedRelay'];

        // On PS 17, we can't modify the cart without resetting it's checksum,
        // or the checkout process will start over (and possibly bug)
        if (version_compare(_PS_VERSION_, '1.7', '>=')) {
            $actions[] = 'updateCartChecksum';
        }

        // Set actions to execute
        call_user_func_array([$handler, 'addActions'], $actions);

        // Process actions chain
        try {
            $handler->process('SelectRelay');
        } catch (Exception $e) {
            $this->jsonError($this->module->l('This pickup point is unavailable.', 'ajaxCheckout'));

            $actionsResult = $handler->getConveyor();

            if (empty($actionsResult['errors'])) {
                return false;
            }

            foreach ($actionsResult['errors'] as $error) {
                $this->jsonError($error);
            }

            return false;
        }

        // Get process result, set errors if any
        $actionsResult = $handler->getConveyor();
        if (!empty($actionsResult['errors'])) {
            $this->jsonError($this->module->l('This pickup point is unavailable.', 'ajaxCheckout'));
            foreach ($actionsResult['errors'] as $error) {
                $this->jsonError($error);
            }
            return false;
        }

        // Save original delivery address in case we change carrier
        // Also warn the FO that the address has changed; the inputs might be
        // obsolete, and a page reload needed
        if (!empty($actionsResult['id_original_address_delivery'])) {
            $this->context->cookie->mondialrelay_id_original_delivery_address = $actionsResult['id_original_address_delivery'];
            $this->context->cookie->write();
        }

        // Render relay summary
        // Multi-shop / multi-theme might not work properly when using
        // the basic "$context->smarty->createTemplate($tpl_name)" syntax, as
        // the template's compile_id will be the same for every shop / theme
        // See https://github.com/PrestaShop/PrestaShop/pull/13804
        $scope = $this->context->smarty->createData($this->context->smarty);
        $scope->assign([
            'selectedRelay' => $actionsResult['selectedRelay'],
            'deliveryMode' => $carrierMethod->delivery_mode,
        ]);

        if (isset($this->context->shop->theme)) {
            // PS17
            $themeName = $this->context->shop->theme->getName();
        } else {
            // PS16
            $themeName = $this->context->shop->theme_name;
        }

        $this->content['relaySummary'] = $this->context->smarty->createTemplate(
            $this->module->getTemplatePath('checkout/relay-summary.tpl'),
            $scope,
            $themeName
        )->fetch();

        return true;
    }
}
