<?php

/**
 * Copyright 2024-2025 Simply.IN Sp. z o.o.
 *
 * NOTICE OF LICENSE
 *
 * Licensed under the EUPL-1.2 or later.
 * You may not use this work except in compliance with the Licence.
 *
 * Copy of the Licence is available at:
 * https://joinup.ec.europa.eu/software/page/eupl
 * It is bundled with this package in the file LICENSE.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Licence is distributed on an as is basis,
 * without warranties or conditions of any kind, either express or implied.
 * Check the Licence for the specific language governing permissions
 * and limitations under the License.
 *
 * @author   Simply.IN Sp. z o.o.
 * @copyright 2024-2025 Simply.IN Sp. z o.o.
 * @license   https://joinup.ec.europa.eu/software/page/eupl
 */

declare(strict_types=1);

namespace SimplyIn\Ajax\Service;

use Configuration;
use Context;
use Simplyin;
use SimplyIn\Api\SimplyInApiClient;

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

class DeliveryFormRenderer
{
    private readonly string $templatesPath;

    public function __construct(
        private readonly Context $context,
        private readonly Simplyin $module,
        private readonly CheckoutSessionService $checkoutSession,
        private readonly ?SimplyInApiClient $api = null,
    ) {
        $this->templatesPath = _PS_MODULE_DIR_ . 'simplyin/views/templates/front/';
    }

    /**
     * @param null|array<string, mixed>|false $data
     */
    public function render(null|array|false $data = null): string
    {
        $data = $this->normalizeData($data);
        $carriersCost = $this->module->getCartCarrierCost();
        $parcelLockers = $this->prepareParcelLockers($data, $this->checkoutSession->getSelectedParcel());

        $this->context->smarty->assign([
            'module_path' => $this->module->getPathUri(),
            'carriers_cost' => $carriersCost,
            '_id' => $data['data']['_id'] ?? '',
            'name' => $data['data']['name'] ?? '',
            'suername' => $data['data']['surname'] ?? '',
            'billing_addresses' => $this->selectActive($data['data']['billingAddresses'] ?? [], $this->checkoutSession->getSelectedBilling()),
            'shipping_addresses' => $this->selectActive($data['data']['shippingAddresses'] ?? [], $this->checkoutSession->getSelectedShipping()),
            'parcel_lockers' => $parcelLockers,
            'parcel_lockers_min_price' => $this->computeMinPrice($parcelLockers, $carriersCost),
            'service_type' => $this->checkoutSession->getServiceType(),
            'delivery_address' => $this->checkoutSession->getSelectedShipping(),
            'billing_address' => $this->checkoutSession->getSelectedBilling(),
            'selected_parcel' => $this->checkoutSession->getSelectedParcel(),
            'form_action' => $this->context->link->getModuleLink('simplyin', 'ajax', ['action' => 'submitForm']),
            'form_type' => 'delivery',
        ]);

        return $this->context->smarty->fetch($this->templatesPath . 'delivery_form.tpl');
    }

    public function renderAjaxForm(string $type): string
    {
        $this->context->smarty->assign([
            'module_path' => $this->module->getPathUri(),
            'form_type' => $type,
            'form_action' => $this->context->link->getModuleLink('simplyin', 'ajax', ['action' => 'submitForm']),
        ]);

        if ($type === 'delivery') {
            return $this->render();
        }

        return $this->context->smarty->fetch($this->templatesPath . $type . '_form.tpl');
    }

    /**
     * @param null|array<string, mixed> $response
     */
    public function renderPopupConfirmation(?array $response): string
    {
        $response ??= [];
        $this->context->smarty->assign([
            'module_path' => $this->module->getPathUri(),
        ]);

        if (!empty($response['userUsedPushNotifications'])) {
            return $this->context->smarty->fetch($this->templatesPath . 'confirmation_form.tpl');
        }

        $this->context->smarty->assign([
            'code' => $response['code'] ?? '',
            'data' => $response['data'] ?? '',
            'message' => str_ireplace(['<br />', '<br/>', '<br>'], "\n", (string) ($response['message'] ?? '')),
            'code_sent_status_message' => (string) ($response['codeSentStatusMessage'] ?? ''),
        ]);

        return $this->context->smarty->fetch($this->templatesPath . 'code_form.tpl');
    }

    /**
     * Shop-login email-OTP challenge popup: 6-digit code entry shown when the shopper's email isn't
     * verified but a registered PS account exists for it. Distinct from code_form.tpl (4 digits,
     * checkout code) — carries data-shop-login-challenge so the shared pin handler routes to the
     * verify action.
     *
     * @param null|array<string, mixed> $response
     */
    public function renderShopLoginChallenge(?array $response = null): string
    {
        $response ??= [];
        $this->context->smarty->assign([
            'module_path' => $this->module->getPathUri(),
            'code_sent_status_message' => (string) ($response['codeSentStatusMessage'] ?? ''),
        ]);

        return $this->context->smarty->fetch($this->templatesPath . 'shop_login_challenge_form.tpl');
    }

    /**
     * @param array<string, mixed> $data
     * @param false|string $selected
     *
     * @return list<array<string, mixed>>
     */
    public function prepareParcelLockers(array $data, false|string $selected): array
    {
        $availableCarriers = $this->module->getAvailableCarriers();
        if (empty($availableCarriers)) {
            return [];
        }

        $parcelLockers = $data['data']['parcelLockers'] ?? [];
        if (!is_array($parcelLockers)) {
            return [];
        }

        $sortBy = $this->sortOrder();
        if ($sortBy !== []) {
            usort($parcelLockers, function ($a, $b) use ($sortBy) {
                $aIndex = array_search(strtolower((string) ($a['providerName'] ?? '')), $sortBy, true);
                $bIndex = array_search(strtolower((string) ($b['providerName'] ?? '')), $sortBy, true);
                $aIndex = $aIndex === false ? PHP_INT_MAX : $aIndex;
                $bIndex = $bIndex === false ? PHP_INT_MAX : $bIndex;
                if ($aIndex !== $bIndex) {
                    return $aIndex <=> $bIndex;
                }

                return strcasecmp((string) ($a['providerName'] ?? ''), (string) ($b['providerName'] ?? ''));
            });
        }

        foreach ($parcelLockers as $key => $locker) {
            if (!isset($locker['providerName']) || !in_array($locker['providerName'], $availableCarriers, true)) {
                unset($parcelLockers[$key]);

                continue;
            }
            if (empty($locker['addressName'])) {
                $parcelLockers[$key]['addressName'] = $locker['lockerId'] ?? '';
            }
            $isSelected = ($selected === $locker['_id'])
                || ($selected === false && ($locker['service_type'] ?? '') === 'parcel_machine');
            $parcelLockers[$key]['selected'] = $isSelected;
            if ($isSelected && $selected === false) {
                $selected = $locker['_id'];
            }
        }

        return array_values($parcelLockers);
    }

    /**
     * @return list<string>
     */
    public function sortOrder(): array
    {
        $raw = json_decode((string) Configuration::get('SIMPLYIN_LOGISTICSPARTNERSORDER'), true);
        if (!is_array($raw)) {
            return [];
        }

        return array_values(array_map('strtolower', array_map('strval', $raw)));
    }

    /**
     * @param list<array<string, mixed>> $addresses
     *
     * @return list<array<string, mixed>>
     */
    public function selectActive(array $addresses, false|string $selected): array
    {
        foreach ($addresses as $key => $address) {
            $addresses[$key]['selected'] = false;
        }
        foreach ($addresses as $key => $address) {
            if (($address['_id'] ?? null) === $selected || $selected === false) {
                $addresses[$key]['selected'] = true;

                break;
            }
        }

        return array_values($addresses);
    }

    /**
     * @param null|array<string, mixed>|false $data
     *
     * @return array{data: array<string, mixed>}
     */
    private function normalizeData(null|array|false $data): array
    {
        if (!is_array($data) || $data === []) {
            // Callers that render the form on its own (the "change data" popup) pass nothing;
            // the account data has to be fetched here or the form renders with no addresses.
            $data = $this->fetchUserData();
        }
        if (!isset($data['data']) && isset($data['userData']) && is_array($data['userData'])) {
            $data['data'] = $data['userData'];
        }
        if (!isset($data['data']) || !is_array($data['data'])) {
            $data['data'] = [];
        }

        return ['data' => $data['data']];
    }

    /**
     * @return array<string, mixed>
     */
    private function fetchUserData(): array
    {
        $authToken = $this->checkoutSession->getAuthToken();
        if ($this->api === null || $authToken === false) {
            return [];
        }

        $result = $this->api->apiCall(false, 'GET', 'userDataCommerce', $authToken);

        return is_array($result) ? $result : [];
    }

    /**
     * @param list<array<string, mixed>> $parcelLockers
     * @param array<string, string> $carriersCost
     */
    private function computeMinPrice(array $parcelLockers, array $carriersCost): int|string
    {
        $prices = [];
        foreach ($parcelLockers as $locker) {
            $provider = $locker['providerName'] ?? null;
            if ($provider !== null && isset($carriersCost[$provider])) {
                $price = (float) str_replace(',', '.', (string) $carriersCost[$provider]);
                $prices[(string) $price] = $carriersCost[$provider];
            }
        }
        if ($prices === []) {
            return 0;
        }
        $minKey = min(array_map('floatval', array_keys($prices)));

        return $prices[(string) $minKey] ?? reset($prices);
    }
}
