<?php
/**
 * NOTICE OF LICENSE.
 *
 * Copyright 2022 Kosmonaft.dev
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 *  @author    Alexandre DEBUSSCHÈRE <alexandre@kosmonaft.dev>
 *  @copyright 2022 - 2023 Kosmonaft.dev
 *  @license   MIT
 */

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

/**
 * Class MRMaxAPI
 */
class MRMaxAPI implements MRMaxAPIInterface
{

    const WS_URL = 'https://api.mondialrelay.com/Web_Services.asmx?WSDL';

    public static $requests_params = [];

    /** @var string */
    protected $tranding;

    /** @var string */
    protected $brand;

    /** @var string */
    protected $key;

    /**
     * @param string $trading
     * @param string $brand
     * @param string $key
     */
    public function __construct($trading, $brand, $key)
    {
        $this->tranding = $trading;
        $this->brand = $brand;
        $this->key = $key;
    }

    /**
     * @param array $params
     * @return stdClass
     */
    public function searchRelayPickup(array $params)
    {
        return $this->call('WSI4_PointRelais_Recherche', $params);
    }

    /**
     * @param string $id
     * @param string $country
     * @return stdClass
     */
    public function getRelayPickupById($id, $country)
    {
        return $this->searchRelayPickup([
            'Pays' => $country,
            'NumPointRelais' => $id
        ]);
    }

    /**
     * @param Order $order
     * @param MRMaxDelivery $delivery
     * @return stdClass
     * @throws PrestaShopException
     */
    public function generateLabel(Order $order, MRMaxDelivery $delivery)
    {
        $delivery_address = new Address($order->id_address_delivery);
        $customer = new Customer($order->id_customer);
        $carrier = new MRMaxCarrier($order->id_carrier);
        $currency = new Currency($order->id_currency);

        $products_weight = (!is_null($delivery->weight) && ((int)$delivery->weight) > 0) ?
            sprintf('%05d', MRMaxTools::convertToKg($delivery->weight) * 1000) :
            null;

        if (!$products_weight) {
            $products = $order->getProducts();
            $products_weight = sprintf('%05d', array_reduce($products, function ($weight, $product) {
                return $weight + (
                        MRMaxTools::convertToKg($product['weight']) * $product['product_quantity'] * 1000
                    );
            }, 0));
            if ($products_weight == '00000' || (int)$products_weight < 100) {
                $products_weight = '00100';
            }
        }

        // Order from BeezUP saves the pickup in address->company and address->address2
        // So get it from there
        if (in_array($carrier->mode, ['24R', '24L', '24X', 'DRI']) &&
            !Tools::strlen($delivery->prid) && Tools::strtolower($order->module) == 'beezup') {
            $company = Tools::strtolower($delivery_address->company);
            if (Tools::strlen($company) && Tools::strpos($company, 'relay')) {
                $relay_pos = Tools::strpos($company, 'relay') + 5;
                $company = Tools::substr($company, $relay_pos);
                $delivery->prid = filter_var($company, FILTER_SANITIZE_NUMBER_INT) ?: null;
            }

            // R09 Relay id: 09286
            if (!Tools::strlen($delivery->prid)) {
                $address_line_2 = Tools::strtolower($delivery_address->address2);
                if (Tools::strlen($address_line_2) && Tools::strpos($address_line_2, 'relay')) {
                    $relay_pos = Tools::strpos($address_line_2, 'relay') + 5;
                    $address_line_2 = Tools::substr($address_line_2, $relay_pos);
                    $delivery->prid = filter_var($address_line_2, FILTER_SANITIZE_NUMBER_INT) ?: null;
                }
            }

            $delivery->save();
        }

        $params = array_map('trim', [
            'ModeCol' => 'CCC' ,
            'ModeLiv' => $carrier->mode,
            'NDossier' => $order->reference,
            'NClient' => $customer->id,
            'Expe_Langage' => Country::getIsoById(Configuration::get('MRMAX_ADDRESS_COUNTRY')),
            'Expe_Ad1' => Configuration::get('MRMAX_ADDRESS_COMPANY'),
            'Expe_Ad2' => '',
            'Expe_Ad3' => Configuration::get('MRMAX_ADDRESS_ADDR1'),
            'Expe_Ad4' => Configuration::get('MRMAX_ADDRESS_ADDR2'),
            'Expe_Ville' => Configuration::get('MRMAX_ADDRESS_CITY'),
            'Expe_CP' => Configuration::get('MRMAX_ADDRESS_ZIP'),
            'Expe_Pays' => Country::getIsoById(Configuration::get('MRMAX_ADDRESS_COUNTRY')),
            'Expe_Tel1' => Configuration::get('MRMAX_ADDRESS_PHONE'),
            'Expe_Tel2' => '',
            'Expe_Mail' => Configuration::get('MRMAX_ADDRESS_EMAIL'),

            'Dest_Langage' => Tools::strtoupper(Language::getIsoById($order->id_lang)),
            'Dest_Ad1' => sprintf(
                '%s %s',
                $delivery_address->firstname,
                $delivery_address->lastname
            ),
            'Dest_Ad2' => MRMaxTools::sanitizeForApiCall($delivery_address->company),
            'Dest_Ad3' => $delivery_address->address1,
            'Dest_Ad4' => MRMaxTools::sanitizeForApiCall($delivery_address->address2),
            'Dest_Ville' => $delivery_address->city,
            'Dest_CP' => $delivery_address->postcode,
            'Dest_Pays' => Country::getIsoById($delivery_address->id_country),
            'Dest_Tel1' => $delivery_address->phone_mobile ?: $delivery_address->phone,
            'Dest_Tel2' => '',
            'Dest_Mail' => $customer->email,

            'Poids' => $products_weight,
            'Longueur' => '',
            'Taille' => '',
            'NbColis' => $delivery->nb_labels ?: 1,
            'CRT_Valeur' => 0,
            'CRT_Devise' => $currency->iso_code,
            'Exp_Valeur' => '',
            'Exp_Devise' => '',

            'COL_Rel_Pays' => '',
            'COL_Rel' => '',

            'LIV_Rel_Pays' => Country::getIsoById($delivery_address->id_country),
            'LIV_Rel' => in_array($carrier->mode, ['24R', '24L', '24X', 'DRI']) ?
                sprintf('%06s', $delivery->prid) : '',

            'Assurance' => $carrier->insurance != 'NA' ? $carrier->insurance : '',
            'Instructions' => ''
        ]);

        self::$requests_params[$order->id] = $params;

        return $this->call('WSI2_CreationEtiquette', $params);
    }

    /**
     * @param array $orders
     * @return array
     * @throws PrestaShopException
     */
    public function generateLabels(array $orders)
    {
        $results = [];

        foreach ($orders as $index => $order) {
            $results[$index] = $this->generateLabel($order[0], $order[1]);
        }

        return $results;
    }

    /**
     * @param string $parcel_number
     * @return stdClass
     */
    public function getTracking($parcel_number)
    {
        return $this->call('WSI2_TracingColisDetaille', [
            'Expedition' => $parcel_number,
            'Langue' => 'FR'
        ]);
    }

    /**
     * @param array $parcel_numbers
     * @return array
     */
    public function getTrackings(array $parcel_numbers)
    {
        $results = [];

        foreach ($parcel_numbers as $index => $parcel_number) {
            $results[$index] = $this->getTracking($parcel_number);
        }

        return $results;
    }

    /**
     * @param string $method
     * @param array $params
     * @return stdClass
     */
    protected function call($method, array $params)
    {
        ini_set('soap.wsdl_cache_enabled', 0);
        ini_set('soap.wsdl_cache_ttl', 0);

        try {
            $client = new SoapClient(self::WS_URL, array('keep_alive' => false));
        } catch (Exception $excep) {
            Tools::dieOrLog($excep->getMessage());
        }

        $client->soap_defencoding = 'UTF-8';
        $client->decode_utf8 = false;

        $params = array_map(function ($param) {
            $param = str_replace('°', '', $param);

            if (mb_detect_encoding((string)$param) != 'UTF-8') {
                iconv('ISO-8859-15', 'UTF-8//TRANSLIT', $param);
            }

            $param = mb_convert_encoding($param, 'HTML-ENTITIES', 'UTF-8');
            $searches = array('&szlig;', '&(..)lig;', '&([aouAOU])uml;', '&(.)[^;]*;');
            $replacements = array('ss', '\\1', '\\1e', '\\1');
            foreach ($searches as $key => $search) {
                $param = mb_ereg_replace($search, $replacements[$key], $param);
            }

            return $param;
        }, $params);
        $params = array_map(array('Tools', 'strtoupper'), $params);

        $params = array_merge(
            ['Enseigne' => $this->tranding],
            $params
        );

        $params['Security'] = $this->getSecurityCode($params);

        return $client->{$method}($params);
    }

    /**
     * Compute security code for Mondial Relay API from class parameters and insert it in parameters.
     */
    public function getSecurityCode(array $params)
    {
        $params_list = $params;
        if (array_key_exists('Texte', $params_list)) {
            unset($params_list['Texte']);
        }

        $security_code = Tools::strtoupper(implode('', $params_list)).$this->key;

        return Tools::strtoupper(md5($security_code));
    }
}
