<?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;
}

require_once _PS_MODULE_DIR_.'mrmax/vendor/autoload.php';

use setasign\Fpdi\Fpdi;
use setasign\Fpdi\PdfParser\StreamReader;

/**
 * Class AdminLabelsMRMaxController
 */
class AdminLabelsMRMaxController extends MRMaxAdminController
{

    /** @var Fpdi */
    protected $fpdi;

    public function __construct()
    {
        parent::__construct();

        $this->fpdi = new Fpdi('P', 'mm', 'A4');
    }

    /**
     * @return string
     * @throws PrestaShopException
     * @throws SmartyException
     */
    public function renderForm()
    {
        if (Tools::isSubmit('viewmrmax')) {
            $this->redirectToOrder();
        }

        if (Tools::isSubmit('submitBulkgeneratemrmax')) {
            $this->generateLabels();
        }

        if (Tools::isSubmit('submitBulkgenerateprintmrmax')) {
            $this->generateLabels();
            $this->context->smarty->assign([
                'print_selected_order_labels' => 'yes'
            ]);
        }

        if (Tools::isSubmit('submitBulkdeletemrmax')) {
            $this->deleteLabels();
        }

        if (!(int)Tools::getValue('submitFiltermrmax')) {
            $this->processResetFilters();
        }

        $this->token = Tools::getAdminTokenLite('AdminLabelsMRMax');
        $this->current_index = AdminController::$currentIndex.'&configure='.$this->module->name;

        // '&mrmaxBox[]=15&mrmaxBox[]=12'
        self::$currentIndex .= '&mrmaxBox[]=15&mrmaxBox[]=12';
        $this->context->smarty->assign('current', self::$currentIndex);

        $form = $this->getOrdersView();

        $this->context->smarty->assign([
            'printlabels_url' => Context::getContext()->link->getModuleLink(
                $this->module->name,
                'printlabels',
                [
                    'token' => Configuration::get('MRMAX_TOKEN')
                ]
            ),
            'update_weight_url' => Context::getContext()->link->getModuleLink(
                $this->module->name,
                'updateweight',
                [
                    'token' => Configuration::get('MRMAX_TOKEN')
                ]
            ),
            'update_nb_labels_url' => Context::getContext()->link->getModuleLink(
                $this->module->name,
                'updatenblabels',
                [
                    'token' => Configuration::get('MRMAX_TOKEN')
                ]
            ),
        ]);

        $form .= $this->context->smarty->fetch(
            $this->module->getLocalPath().'views/templates/admin/labels.tpl'
        );

        $form .= $this->context->smarty->fetch(
            $this->module->getLocalPath().'views/templates/admin/prestui/ps-tags.tpl'
        );

        return $form;
    }

    /**
     * @param bool $isNewTheme
     * @return void
     */
    public function setMedia($isNewTheme = false)
    {
        parent::setMedia($isNewTheme);

        $this->addCSS(
            $this->module->getPathUri().'views/css/labels.css'
        );

        $this->addJS(
            $this->module->getPathUri().'views/js/labels.js'
        );
    }

    /**
     * @return false|string
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public function getOrdersView()
    {
        $fields_list = [
            'id_order' => [
                'title' => 'ID',
                'filter_key' => 'id_order',
                'align' => 'center',
                'class' => 'fixed-width-xs',
                'remove_onclick' => true
            ],
            'reference' => [
                'title' => $this->l('Reference'),
                'filter_key' => 'reference',
                'remove_onclick' => true
            ],
            'country' => [
                'type' => 'select',
                'list' => $this->getCountriesWithOrders(),
                'title' => $this->l('Delivery'),
                'filter_key' => 'country',
                'remove_onclick' => true
            ],
            'customer' => [
                'title' => $this->l('Customer'),
                'filter_key' => 'customer',
                'remove_onclick' => true
            ],
            'total_paid_tax_incl' => [
                'title' => $this->l('Total'),
                'filter_key' => 'total',
                'remove_onclick' => true
            ],
            'payment' => [
                'title' => $this->l('Payment'),
                'filter_key' => 'payment',
                'remove_onclick' => true
            ],
            'current_state' => [
                'type' => 'select',
                'list' => array_reduce(
                    OrderState::getOrderStates(Context::getContext()->language->id),
                    function ($acc, $order_state) {
                        $acc[$order_state['id_order_state']] = $order_state['name'];
                        return $acc;
                    },
                    []
                ),
                'title' => $this->l('State'),
                'filter_key' => 'current_state',
                'color' => 'color_value',
                'remove_onclick' => true
            ],
            'carrier' => [
                'type' => 'select',
                'list' => array_reduce(
                    MRMaxCarrier::getMRCarrierNames(),
                    function ($acc, $name) {
                        $acc[$name] = $name;
                        return $acc;
                    },
                    []
                ),
                'title' => $this->l('Delivery mode'),
                'filter_key' => 'carrier',
                'remove_onclick' => true
            ],
            'weight' => [
                'title' => sprintf(
                    '%s (%s)',
                    $this->l('Weight'),
                    Configuration::get('PS_WEIGHT_UNIT')
                ),
                'remove_onclick' => true
            ],
            'date' => [
                'type' => 'datetime',
                'title' => $this->l('Date'),
                'filter_key' => 'date',
                'remove_onclick' => true
            ],
            'nb_labels' => [
                'title' => $this->l('Nb. Label'),
                'remove_onclick' => true
            ],
            'label' => [
                'type' => 'bool',
                'title' => $this->l('Label'),
                'icon' => [
                    'yes' => 'enabled.gif',
                    'default' => 'disabled.gif'
                ],
                'remove_onclick' => true
            ]
        ];

        $content = array();
        foreach (MRMaxDelivery::getPaginatedOrders() as $order) {
            $order_state = new OrderState($order['current_state'], Context::getContext()->language->id);
            if (!Validate::isLoadedObject($order_state)) {
                continue;
            }

            $country = $this->getOrderCountry($order['id_address_delivery']);
            $customer = MRMaxCustomer::getNames($order['id_customer']);
            $total = Tools::ps_round($order['total_paid_tax_incl'], 2);
            $carrier = MRMaxCarrier::getNameById($order['id_carrier']);
            $label = $this->orderHasLabel($order);

            if ($this->isOrderFiltered($order, $order_state, $country, $customer, $total, $carrier, $label)) {
                continue;
            }

            $content[] = [
                'id_order' => $order['id_order'],
                'reference' => $order['reference'],
                'country' => $country,
                'customer' => $customer,
                'total_paid_tax_incl' => $this->getTotalPaidWithCurrency(
                    $total,
                    $order['id_currency']
                ),
                'payment' => $order['payment'],
                'current_state' => $order_state->name,
                'carrier' => $carrier,
                'weight' => isset($order['weight']) && $order['weight'] ?
                    (float)$order['weight'] :
                    (float)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
                    'SELECT SUM(`product_weight` * `product_quantity`)
                    FROM `'._DB_PREFIX_.'order_detail`
                    WHERE `id_order` = '.(int)$order['id_order']
                ),
                'color_value' => $order_state->color,
                'date' => $order['date_add'],
                'nb_labels' => isset($order['nb_labels']) && $order['nb_labels'] > 0 ?
                    $order['nb_labels'] :
                    1,
                'label' => $label ? 'yes' : 'no'
            ];
        }

        $helper = new HelperList();
        $helper->imageType = 'jpg';
        $helper->shopLinkType = '';
        $helper->simple_header = false;
        $helper->actions = ['view', 'print'];
        $helper->show_toolbar = false;
        $helper->module = $this->module;
        $helper->listTotal = count($content);
        $helper->identifier = 'id_order';
        $helper->title = $this->l('Orders');
        $this->list_id = $this->module->name;
        $helper->table = $this->module->name;
        $helper->token = $this->token;
        $helper->currentIndex = $this->current_index.'&mrmaxBox[]=15&mrmaxBox[]=12';

        $helper->bulk_actions = [
            'generate' => [
                'text' => $this->l('Generate label(s)'),
                'icon' => 'icon-file',
            ],
            'print' => [
                'text' => $this->l('Print label(s)'),
                'icon' => 'icon-print',
            ],
            'divider1' => array(
                'text' => 'divider'
            ),
            'delete' => [
                'text' => $this->l('Delete label(s)'),
                'icon' => 'icon-trash',
            ]
        ];

        $helper->force_show_bulk_actions = true;

        return $helper->generateList($content, $fields_list);
    }

    private function getCountriesWithOrders()
    {
        $countries_with_orders = Db::getInstance()->executeS(
            'SELECT DISTINCT(a.`id_country`)
            FROM `'._DB_PREFIX_.'address` a
            LEFT JOIN `'._DB_PREFIX_.'orders` o ON o.`id_address_delivery` = a.`id_address`'
        );

        $countries_with_orders = array_column($countries_with_orders, 'id_country');

        return array_reduce(
            Country::getCountries(Context::getContext()->language->id),
            function ($acc, $country) use ($countries_with_orders) {
                if (in_array($country['id_country'], $countries_with_orders)) {
                    $acc[$country['name']] = $country['name'];
                }
                return $acc;
            },
            []
        );
    }

    /**
     * @param string $token
     * @param int $id
     * @param string $name
     * @return false|string
     * @throws SmartyException
     */
    public function displayPrintLink($token, $id, $name)
    {
        unset($token, $name);

        $order = new Order($id);
        if (!Validate::isLoadedObject($order)) {
            return false;
        }

        $this->context->smarty->assign(array(
            'url' => Context::getContext()->link->getModuleLink($this->module->name, 'printlabels', [
                'token' => Configuration::get('MRMAX_TOKEN'),
                'ids' => $id
            ]),
            'disabled' => !$this->orderHasLabel((array)$order),
            'id' => $id
        ));

        return $this->context->smarty->fetch(
            $this->module->getLocalPath().'views/templates/admin/print_button.tpl'
        );
    }

    /**
     * @param array $order
     * @param OrderState $order_state
     * @param string $country
     * @param string $customer
     * @param float $total
     * @param string $carrier
     * @param bool $label
     * @return bool
     * @throws Exception
     */
    private function isOrderFiltered($order, $order_state, $country, $customer, $total, $carrier, $label)
    {
        if (!(int)Tools::getValue('submitFiltermrmax')) {
            return false;
        }

        $filter_id_order = Tools::getValue('mrmaxFilter_id_order');
        $filter_reference = Tools::getValue('mrmaxFilter_reference');
        $filter_country = Tools::getValue('mrmaxFilter_country');
        $filter_customer = Tools::getValue('mrmaxFilter_customer');
        $filter_total = Tools::getValue('mrmaxFilter_total');
        $filter_payment = Tools::getValue('mrmaxFilter_payment');
        $filter_current_state = Tools::getValue('mrmaxFilter_current_state');
        $filter_carrier = Tools::getValue('mrmaxFilter_carrier');
        $filter_label = Tools::getValue('mrmaxFilter_label');

        $filter_date_min = Tools::getValue('mrmaxFilter_date')[0];
        $filter_date_max = Tools::getValue('mrmaxFilter_date')[1];
        if ($filter_date_min && !Tools::strlen($filter_date_max)) {
            $filter_date_max = date('Y-m-d');
        }
        if ($filter_date_max && !Tools::strlen($filter_date_min)) {
            $filter_date_min = date('2000-01-01');
        }

        if (Tools::strlen($filter_id_order) && $filter_id_order != $order['id_order']) {
            return true;
        }

        if (Tools::strlen($filter_reference) && $filter_reference != $order['reference']) {
            return true;
        }

        if (Tools::strlen($filter_country) && $filter_country != $country) {
            return true;
        }

        if (Tools::strlen($filter_customer) && strpos($customer, $filter_customer) === false) {
            return true;
        }

        if (Tools::strlen($filter_total) && $filter_total != $total) {
            return true;
        }

        if (Tools::strlen($filter_payment) && strpos($order['payment'], $filter_payment) === false) {
            return true;
        }

        if (Tools::strlen($filter_current_state) && $filter_current_state != $order_state->id) {
            return true;
        }

        if (Tools::strlen($filter_carrier) && $filter_carrier != $carrier) {
            return true;
        }

        if (Tools::strlen($filter_date_min) && Tools::strlen($filter_date_max)) {
            $today = new DateTime($order['date_add']);
            $min = new DateTime($filter_date_min);
            $max = new DateTime($filter_date_max);

            if ($today->getTimestamp() < $min->getTimestamp() ||
                $today->getTimestamp() > $max->getTimestamp()) {
                return true;
            }
        }

        if (Tools::strlen($filter_label) && $filter_label != $label) {
            return true;
        }

        return false;
    }

    /**
     * @param array $order
     * @return bool
     */
    private function orderHasLabel($order)
    {
        $id_order = (int)Db::getInstance()->getValue(
            'SELECT `id_order` FROM `' . _DB_PREFIX_ . 'orders` WHERE `id_cart` = '.(int)$order['id_cart']
        );

        $order = new Order($id_order);
        if (!Validate::isLoadedObject($order)) {
            return false;
        }

        $shipping_number = array_reduce($order->getShipping(), function ($number, $shipping) {
            if (!MRMaxTools::strlen($number) && MRMaxTools::strlen($shipping['tracking_number'])) {
                $number = $shipping['tracking_number'];
            }

            return $number;
        }, '');

        // Check file exists
        $label_path = _PS_MODULE_DIR_.$this->module->name.'/download/'.$shipping_number.'.pdf';
        if (!file_exists($label_path)) {
            return false;
        }

        // Check content as well to make sure it is a PDF file
        $file = StreamReader::createByFile($label_path);
        try {
            $this->fpdi->setSourceFile($file);
        } catch (Exception $exception) {
            unset($exception);
            return false;
        }

        return true;
    }

    /**
     * @param string|float $total
     * @param int $id_currency
     * @return string
     */
    private function getTotalPaidWithCurrency($total, $id_currency)
    {
        $currency = new Currency((int)$id_currency);
        if (Validate::isLoadedObject($currency)) {
            $total = sprintf(
                '%s %s',
                (string)$total,
                isset($currency->symbol) ? $currency->symbol : $currency->sign
            );
        }

        return (string)$total;
    }

    /**
     * @param int $id_address_delivery
     * @return string
     */
    private function getOrderCountry($id_address_delivery)
    {
        $address = new Address((int)$id_address_delivery);
        if (!Validate::isLoadedObject($address)) {
            return '';
        }

        return $address->country;
    }

    /**
     * @return void
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    protected function generateLabels()
    {
        $order_ids = Tools::getValue('mrmaxBox');
        if (!is_array($order_ids) || !count($order_ids)) {
            $this->errors[] = $this->l('No orders to process...');
            return;
        }

        $orders = [];
        foreach ($order_ids as $order_id) {
            $order = new Order($order_id);
            $delivery = MRMaxDelivery::getByIdCart($order->id_cart);

            $shipping_number = array_reduce($order->getShipping(), function ($number, $shipping) {
                if (!MRMaxTools::strlen($number) && MRMaxTools::strlen($shipping['tracking_number'])) {
                    $number = $shipping['tracking_number'];
                }

                return $number;
            }, '');

            if ($shipping_number || !Validate::isLoadedObject($delivery)) {
                continue;
            }

            $orders[$order_id] = [$order, $delivery];
        }

        $id_order_state = Configuration::get('MRMAX_ORDER_STATE');

        foreach (MRMaxAPIProvider::getAPIInstance()->generateLabels($orders) as $order_id => $response) {
            if ($response->WSI2_CreationEtiquetteResult->STAT != 0) {
                $this->context->smarty->assign([
                    'mrmax_error_summary' => $this->l(sprintf(
                        'An error occured with order #%s: %s.',
                        $order_id,
                        MRMaxStat::getMessageFromStat($response->WSI2_CreationEtiquetteResult->STAT)
                    )),
                    'mrmax_error_details' => var_export(MRMaxAPI::$requests_params[$order_id], true)
                ]);

                $this->errors[] = $this->context->smarty->fetch(
                    $this->module->getLocalPath().'views/templates/admin/details_error.tpl'
                );

                continue;
            }

            $parcel_number = $response->WSI2_CreationEtiquetteResult->ExpeditionNum;
            if (!Tools::strlen($parcel_number)) {
                $this->errors[] = $this->l(sprintf(
                    'Unable to find tracking number for order #%s.',
                    $order_id
                ));
                continue;
            }

            $label_url = (string)$response->WSI2_CreationEtiquetteResult->URL_Etiquette;

            if (!filter_var($label_url, FILTER_VALIDATE_URL)) {
                $label_format = Configuration::get('MRMAX_LABEL_FORMAT');
                $label_url = 'https://www.mondialrelay.fr'.$label_url;
                if ($label_format != 'A4') {
                    $label_url = str_replace('format=A4', 'format='.$label_format, $label_url);
                }
            }

            // Saving label on server
            $filename = _PS_MODULE_DIR_.$this->module->name.'/download/'.$parcel_number.'.pdf';

            $write_success = file_put_contents($filename, Tools::file_get_contents($label_url, false, null, 5, true));
            if (!$write_success) {
                sleep(1);

                if (!file_put_contents($filename, Tools::file_get_contents($label_url, false, null, 5, true))) {
                    $this->errors[] = $this->l(sprintf(
                        'Unable to save label on server for order #%s.',
                        $order_id
                    ));
                    continue;
                }
            }

            /** @var Order $order */
            $order = $orders[$order_id][0];

            // Tracking number
            $order->shipping_number = $parcel_number;
            $order->update();

            $id_order_carrier = (int)Db::getInstance()->getValue(
                'SELECT `id_order_carrier`
                    FROM `'._DB_PREFIX_.'order_carrier`
                    WHERE `id_order` = '.(int)$order->id
            );

            if ($id_order_carrier) {
                $order_carrier = new OrderCarrier($id_order_carrier);
                if (Validate::isLoadedObject($order_carrier)) {
                    $order_carrier->tracking_number = $parcel_number;
                    $order_carrier->update();
                }
            }

            // Update order state
            if ($id_order_state && $id_order_state != $order->current_state) {
                $order->setCurrentState($id_order_state, Context::getContext()->employee->id);
            }

            Hook::exec('actionAdminOrdersTrackingNumberUpdate', array(
                'order' => $order,
                'customer' => new Customer($order->id_customer),
                'carrier' => new Carrier($order->id_carrier)
            ));
        }
    }

    /**
     * @return void
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public function deleteLabels()
    {
        $order_ids = Tools::getValue('mrmaxBox');
        if (!is_array($order_ids) || !count($order_ids)) {
            return;
        }

        $base_path = _PS_MODULE_DIR_.'mrmax/download/';

        foreach ($order_ids as $order_id) {
            $order = new Order($order_id);
            if (!Validate::isLoadedObject($order)) {
                continue;
            }

            $shipping_number = array_reduce($order->getShipping(), function ($number, $shipping) {
                if (!MRMaxTools::strlen($number) && MRMaxTools::strlen($shipping['tracking_number'])) {
                    $number = $shipping['tracking_number'];
                }

                return $number;
            }, '');

            $order->shipping_number = '';
            $order->update();

            $id_order_carrier = (int)Db::getInstance()->getValue(
                'SELECT `id_order_carrier`
                    FROM `'._DB_PREFIX_.'order_carrier`
                    WHERE `id_order` = '.(int)$order->id
            );

            if ($id_order_carrier) {
                $order_carrier = new OrderCarrier($id_order_carrier);
                if (Validate::isLoadedObject($order_carrier)) {
                    $order_carrier->tracking_number = '';
                    $order_carrier->update();
                }
            }

            // Label
            $filename = sprintf('%s%s.pdf', $base_path, $shipping_number);
            if (file_exists($filename)) {
                unlink($filename);
            }
        }
    }
}
