<?php
/**
 * 2018 Paysera
 *
 * 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.
 *
 * @author Paysera <plugins@paysera.com>
 * @copyright 2018 Paysera
 * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
 * International Registered Trademark & Property of Paysera
 */
declare(strict_types=1);

namespace Paysera\Delivery;

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

use Paysera\Delivery\Service\DeliveryApiClient;
use Paysera\DeliveryApi\MerchantClient\Entity\ShipmentMethod;
use PayseraDelivery;

class PayseraDeliveryCarriers
{
    private const GATEWAY_CODE_KEY = 'gateway_code';
    private const SHIPPING_METHOD_KEY = 'shipping_method';
    private const SHIPPING_METHODS_KEY = 'shipping_methods';
    private const DESCRIPTION_KEY = 'description';
    private const SHIPMENT_CODE_KEY = 'shipment_code';
    private const CARRIER_LOGO_KEY = 'carrier_logo';
    private const ENABLED_KEY = 'enabled';
    private const CODE_KEY = 'code';
    private const TYPE_KEY = 'type';
    private const CARRIER_ID_KEY = 'carrier_id';
    private const CARRIER_REF_ID_KEY = 'reference_id';

    /**
     * @var PayseraDelivery
     */
    private $module;
    /**
     * @var PayseraDeliveryConfiguration
     */
    private $configuration;
    /**
     * @var array<int|string, array<string, mixed>>
     */
    private $savedGateways;
    /**
     * @var array<int, array<string, int|string|array<int, array<string, string>>>>
     */
    private $savedCarriers;

    public function __construct(PayseraDelivery $module, PayseraDeliveryConfiguration $configuration)
    {
        $this->configuration = $configuration;
        $this->module = $module;

        if (count($this->configuration->getDeliveryGateways()) === 0) {
            $this->setUpDefaultCarriersData();
        }

        $this->disableCarriersForTerminal([
            'tnt',
            'itella',
        ]);
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function getCarriersSettingsFormData(): array
    {
        $settingsFormData = [];
        $context = \Context::getContext();

        foreach ($this->configuration->getDeliveryGateways() as $gatewayCode => $gatewayData) {
            $settingsFormData[$gatewayData[self::CODE_KEY]] = [
                'settings_link' => sprintf(
                    '<a href="%s" class="btn paysera-btn">%s</a>',
                    $context !== null ? $context->link->getAdminLink('AdminCarriers') : '',
                    $this->module->l('Settings', 'payseradeliverycarriers')
                ),
                'image' => sprintf(
                    '<img src="%s" alt="%s"/>',
                    is_string($gatewayData[self::CARRIER_LOGO_KEY]) ? $gatewayData[self::CARRIER_LOGO_KEY] : '',
                    is_string($gatewayData[self::DESCRIPTION_KEY]) ? $gatewayData[self::DESCRIPTION_KEY] : ''
                ),
                self::ENABLED_KEY => $gatewayData[self::ENABLED_KEY],
                self::DESCRIPTION_KEY => $gatewayData[self::DESCRIPTION_KEY],
                self::SHIPPING_METHODS_KEY => $gatewayData[self::SHIPPING_METHODS_KEY],
            ];
        }

        return $settingsFormData;
    }

    /**
     * @param array<string, string> $sendData
     *
     * @return bool
     */
    public function updateCarriersSavedData(array $sendData): bool
    {
        if (isset($sendData[self::GATEWAY_CODE_KEY]) === true) {
            if (isset($sendData[self::ENABLED_KEY]) === true) {
                $this->toggleGatewayStatus($sendData);
            }

            if (isset($sendData[self::SHIPPING_METHOD_KEY]) === true) {
                $this->enableShippingMethod($sendData);
            }
        }

        return true;
    }

    public function setUpDefaultCarriersData(): void
    {
        if (
            $this->configuration->getProjectId() === null
            || $this->configuration->getProjectPassword() === null
        ) {
            return;
        }

        $deliveryApiClient = $this->module
            ->getServiceContainer()
            ->getService('paysera.payseradelivery.service.delivery_api_client')
        ;

        $existingGateways = $this->configuration->getDeliveryGateways();
        $fetchedGateways = $deliveryApiClient->getDeliveryGateways();

        if (!$this->hasGatewaysChanged($existingGateways, $fetchedGateways)) {
            return;
        }

        $savedGateways = [];
        foreach ($deliveryApiClient->getDeliveryGateways() as $deliveryGateway) {
            if ($deliveryGateway->isEnabled() === true) {
                $savedGateways[$deliveryGateway->getCode()] = [
                    self::CARRIER_LOGO_KEY => $deliveryGateway->getLogo(),
                    self::DESCRIPTION_KEY => $deliveryGateway->getDescription(),
                    self::ENABLED_KEY => 0,
                    self::CODE_KEY => $deliveryGateway->getCode(),
                    self::SHIPPING_METHODS_KEY => [],
                ];

                foreach ($deliveryApiClient->getShipmentMethods() as $shipmentMethod) {
                    if ($shipmentMethod->isEnabled() === true) {
                        $savedGateways[$deliveryGateway->getCode()][self::SHIPPING_METHODS_KEY][] = [
                            self::TYPE_KEY => $this->resolveDeliveryGatewayReceiverType($shipmentMethod->getCode()),
                            self::DESCRIPTION_KEY => $shipmentMethod->getDescription(),
                            self::SHIPMENT_CODE_KEY => $shipmentMethod->getCode(),
                        ];
                    }
                }
            }
        }

        $this->configuration->setDeliveryGateways($savedGateways);
    }

    public function disableCarriersForTerminal(array $gatewayCodes): void
    {
        $savedGateways = $this->configuration->getDeliveryGateways();
        foreach ($savedGateways as $key => $gateway) {
            if (in_array($gateway['code'], $gatewayCodes, true)) {
                foreach ($gateway['shipping_methods'] as $shippingMethodKey => $shipping_method) {
                    if ($shipping_method['shipment_code'] !== ShipmentMethod::CODE_COURIER2COURIER) {
                        unset($savedGateways[$key]['shipping_methods'][$shippingMethodKey]);
                    }
                }
            }
        }

        $this->configuration->setDeliveryGateways($savedGateways);
    }

    /**
     * @param int $carrierId
     *
     * @return array<string, int|string>|null
     */
    public function getCarrierById(int $carrierId): ?array
    {
        foreach ($this->configuration->getDeliveryCarriers() as $carrier) {
            if ((int) $carrier[self::CARRIER_ID_KEY] === $carrierId) {
                return $carrier;
            }
        }

        return null;
    }

    public function updateCarrierId(int $referenceId, int $newId): bool
    {
        $carriers = [];
        foreach ($this->configuration->getDeliveryCarriers() as $carrierCode => $carrier) {
            if ((int) $carrier[self::CARRIER_REF_ID_KEY] === $referenceId) {
                $carrier[self::CARRIER_ID_KEY] = $newId;
            }

            $carriers[$carrierCode] = $carrier;
        }
        $this->configuration->setDeliveryCarriers($carriers);

        return true;
    }

    public function resolveDeliveryGatewayReceiverType(string $carrierCode): ?string
    {
        $explodedCarrierCode = explode('2', $carrierCode);
        $deliveryGatewayReceiverType = end($explodedCarrierCode);

        return $deliveryGatewayReceiverType === '' ? null : $deliveryGatewayReceiverType;
    }

    private function hasGatewaysChanged(array $existingGateways, array $fetchedGateways): bool
    {
        $enabledFetchedGateways = array_filter($fetchedGateways, function ($gateway) {
            return $gateway->isEnabled();
        });

        $existingCodes = array_column($existingGateways, self::CODE_KEY);
        $fetchedCodes = array_map(function ($gateway) {
            return $gateway->getCode();
        }, $enabledFetchedGateways);

        sort($existingCodes);
        sort($fetchedCodes);

        return $existingCodes !== $fetchedCodes;
    }

    private function setGatewaysAndCarriers(): void
    {
        $this->savedGateways = $this->configuration->getDeliveryGateways();
        $this->savedCarriers = $this->configuration->getDeliveryCarriers();
    }

    private function saveGatewaysAndCarriers(): void
    {
        $this->configuration->setDeliveryCarriers($this->savedCarriers);
        $this->configuration->setDeliveryGateways($this->savedGateways);
    }

    /**
     * @param array<string, string> $sendData
     *
     * @return void
     */
    private function toggleGatewayStatus(array $sendData): void
    {
        $this->setGatewaysAndCarriers();

        $gatewayItem = $this->savedGateways[$sendData[self::GATEWAY_CODE_KEY]];
        $gatewayCode = $sendData[self::GATEWAY_CODE_KEY];
        $enableCarrier = (int)$sendData[self::ENABLED_KEY] === 0;

        $gatewayItem[self::ENABLED_KEY] = (int) $enableCarrier;
        foreach ($this->savedCarriers as $deliveryCarrier) {
            if ($gatewayCode !== $deliveryCarrier[self::CODE_KEY]) {
                continue;
            }

            $carrierId = (int)$deliveryCarrier[self::CARRIER_ID_KEY];
            if ($enableCarrier === true) {
                $this->enableCarrier($carrierId);
            } else {
                $this->disableCarrier($carrierId);
            }
        }
        $this->savedGateways[$sendData[self::GATEWAY_CODE_KEY]] = $gatewayItem;
        $this->saveGatewaysAndCarriers();
    }

    /**
     * @param array<string, string> $sendData
     *
     * @return void
     */
    private function enableShippingMethod(array $sendData): void
    {
        $this->setGatewaysAndCarriers();
        /** @var array<string, int|string|array<int, array<string, string>>> */
        $gatewayItem = $this->savedGateways[$sendData[self::GATEWAY_CODE_KEY]];
        $shippingMethods = is_array($gatewayItem[self::SHIPPING_METHODS_KEY])
            ? (array)$gatewayItem[self::SHIPPING_METHODS_KEY][(int)$sendData[self::SHIPPING_METHOD_KEY]]
            : [];

        $createdCarrier = $this->createCarrier(
            sprintf(
                '%s %s',
                is_string($gatewayItem[self::DESCRIPTION_KEY]) ? $gatewayItem[self::DESCRIPTION_KEY] : '',
                $shippingMethods[self::DESCRIPTION_KEY]
            ),
            is_string($gatewayItem[self::CARRIER_LOGO_KEY]) ? $gatewayItem[self::CARRIER_LOGO_KEY] : ''
        );

        if ($createdCarrier !== false && $createdCarrier !== null) {

            $this->savedCarriers[] = [
                self::GATEWAY_CODE_KEY => $sendData[self::GATEWAY_CODE_KEY],
                self::CARRIER_ID_KEY => (int)$createdCarrier->id,
                self::CARRIER_REF_ID_KEY => (int)$createdCarrier->id,
                self::CODE_KEY => $gatewayItem[self::CODE_KEY],
                self::TYPE_KEY => $shippingMethods[self::TYPE_KEY],
                self::SHIPMENT_CODE_KEY => $shippingMethods[self::SHIPMENT_CODE_KEY],
            ];
        }

        $this->savedGateways[$sendData[self::GATEWAY_CODE_KEY]] = $gatewayItem;
        $this->saveGatewaysAndCarriers();
    }

    private function createCarrier(string $carrierName, string $carrierLogo): ?\Carrier
    {
        $carrier = new \Carrier();
        $carrier->name = $carrierName;
        $carrier->is_module = false;
        $carrier->active = true;
        $carrier->is_free = false;
        $carrier->max_weight = PayseraDeliveryConfiguration::DEFAULT_MAX_WEIGHT;
        $carrier->range_behavior = 1;
        $carrier->shipping_method = 2;

        /**
         * @var array<string, string> $language
         */
        foreach (\Language::getLanguages() as $language) {
            $carrier->delay[(int) $language['id_lang']] = '-';
        }

        $added = $carrier->add();

        if ($added === true) {
            $carrierGroups = [];

            foreach (\Group::getGroups(\Configuration::get('PS_LANG_DEFAULT')) as $group) {
                $carrierGroups[] = $group['id_group'];
            }

            $carrier->setGroups($carrierGroups);

            $zones = \Zone::getZones(true);
            foreach ($zones as $zone) {
                $carrier->addZone((int)$zone['id_zone']);
            }

            try {
                copy($carrierLogo, _PS_SHIP_IMG_DIR_ . DIRECTORY_SEPARATOR . $carrier->id . '.jpg');
            } catch (\Exception $exception) {
                throw new \PrestaShopException(sprintf('Carrier %s logo cannot be uploaded', $carrierName));
            }

            return $carrier;
        }

        return null;
    }

    private function deleteCarrier(int $carrierId): bool
    {
        return (new \Carrier($carrierId))->delete();
    }

    private function enableCarrier(int $carrierId): void
    {
        $carrier = new \Carrier($carrierId);
        $carrier->active = true;
        $carrier->update();
    }

    private function disableCarrier(int $carrierId): void
    {
        $carrier = new \Carrier($carrierId);
        $carrier->active = false;
        $carrier->update();
    }
}
