<?php

namespace Opencart\Extension\Alsendo\Services;

require_once(DIR_EXTENSION . 'alsendo/src/Helpers/Country.php');
require_once(DIR_EXTENSION . 'alsendo/src/DTO/Entity/AlsendoRegionEntity.php');
require_once(DIR_EXTENSION . 'alsendo/src/DTO/Entity/ServiceEntity.php');
require_once(DIR_EXTENSION . 'alsendo/src/DTO/OrderShipmentSubmitDTO.php');
require_once(DIR_EXTENSION . 'alsendo/src/DTO/FullOrderDTO.php');
require_once(DIR_EXTENSION . 'alsendo/src/Providers/AdditionalSettingsDataProvider.php');
require_once(DIR_EXTENSION . 'alsendo/src/Repositories/SenderAddressRepository.php');
require_once(DIR_EXTENSION . 'alsendo/src/Repositories/ConfigRepository.php');
require_once(DIR_EXTENSION . 'alsendo/src/Services/MapWidgetService.php');

use Alsendo\AlsendoWrapper\ApiWrapper;
use Alsendo\AlsendoWrapper\Exception\ValidationException;
use Alsendo\AlsendoWrapper\Model\Address;
use Alsendo\AlsendoWrapper\Model\Cod;
use Alsendo\AlsendoWrapper\Model\Contact;
use Alsendo\AlsendoWrapper\Model\Notification;
use Alsendo\AlsendoWrapper\Model\NotificationDetail;
use Alsendo\AlsendoWrapper\Model\Order\OrderRequest;
use Alsendo\AlsendoWrapper\Model\Pickup;
use Alsendo\AlsendoWrapper\Model\Shipment;
use Opencart\Extension\Alsendo\DTO\Entity\AlsendoRegionEntity;
use Opencart\Extension\Alsendo\DTO\Entity\ServiceEntity;
use Opencart\Extension\Alsendo\DTO\FullOrderDTO;
use Opencart\Extension\Alsendo\Helpers\Country;
use Opencart\Extension\Alsendo\Models\Config;
use Opencart\Extension\Alsendo\Models\WrapperResult;
use Opencart\Extension\Alsendo\Providers\AdditionalSettingsDataProvider;
use Opencart\Extension\Alsendo\Repositories\ConfigRepository;
use Opencart\Extension\Alsendo\Repositories\SenderAddressRepository;
use Opencart\Extension\Alsendo\Repositories\ServicesRepository;
use Opencart\System\Engine\Registry;

class WrapperService
{
    private Registry $registry;
    private string $extensionDir;
    private ServicesRepository $servicesRepository;
    private AdditionalSettingsDataProvider $additionalSettingsDataProvider;
    private SenderAddressRepository $senderAddressRepository;
    private ?ConfigRepository $configRepository;
    private MapWidgetService $mapWidgetService;

    public function __construct(
        Registry $registry,
        string $extensionDir,
        ServicesRepository $servicesRepository,
        AdditionalSettingsDataProvider $additionalSettingsDataProvider,
        SenderAddressRepository $senderAddressRepository,
        ?ConfigRepository $configRepository = null,
    ) {
        $this->registry = $registry;
        $this->extensionDir = $extensionDir;
        $this->servicesRepository = $servicesRepository;
        $this->additionalSettingsDataProvider = $additionalSettingsDataProvider;
        $this->senderAddressRepository = $senderAddressRepository;
        $this->configRepository = $configRepository;

        $this->registry->load->model('extension/alsendo/module/alsendo_region');
        /** @var AlsendoRegionEntity $currentRegion */
        $currentRegion = $this->registry->model_extension_alsendo_module_alsendo_region->getCurrentRegion();

        $this->mapWidgetService = new MapWidgetService($currentRegion->code);
    }

    private function getApiClient(Config $config)
    {
        if ($config->getRegion() === 'romania') {
            $this->refreshEcoletTokenIfNeeded($config);
        }

        require_once($this->extensionDir . 'wrapper/vendor/autoload.php');
        $wrapper = new ApiWrapper(
            $config->getWrapperApiName(),
            $config->getApiConfig(),
            $config->getPlatformName()
        );
        return $wrapper->getApiClient();
    }

    private function withApiClient(Config $config, callable $fn, ?string $errorMessage = null): WrapperResult
    {
        if (!$config->isComplete()) {
            return new WrapperResult(false, 'Configuration is incomplete');
        }
        try {
            $apiClient = $this->getApiClient($config);
            $result = $fn($apiClient);
            if ($result instanceof WrapperResult) {
                return $result;
            }
            if ($result === null) {
                return new WrapperResult(true, '');
            }
            return new WrapperResult(true, '', is_array($result) ? $result : ['data' => $result]);
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Exception $e) {
            return new WrapperResult(false, $errorMessage ?? 'Error', [], $e->getMessage());
        }
    }

    public function initializeWrapper(Config $config): WrapperResult
    {
        return $this->withApiClient($config, function ($apiClient) use ($config) {
            $data = [
                'message' => 'Wrapper initialized successfully',
                'region' => ucfirst($config->getRegion()),
                'client_type' => basename(str_replace('\\', '/', get_class($apiClient))),
                'environment' => 'Test Environment'
            ];
            return new WrapperResult(true, 'Wrapper initialized successfully', $data);
        }, 'Failed to initialize wrapper');
    }

    public function getAddressBookList(Config $config): WrapperResult
    {
        if ($config->getRegion() !== 'czechia') {
            return new WrapperResult(true, '', ['addresses' => []]);
        }
        return $this->withApiClient($config, function ($apiClient) {
            $response = $apiClient->getAddressBookList();
            $addresses = [];
            foreach ($response->addresses as $id => $entry) {
                $addresses[$id] = [
                    'id' => $entry->id,
                    'company' => $entry->company ?? '',
                    'firstname' => $entry->firstname ?? '',
                    'surname' => $entry->surname ?? '',
                    'street' => $entry->street ?? '',
                    'city' => $entry->city ?? '',
                    'zip' => $entry->zip ?? '',
                    'country' => $entry->country ?? '',
                    'phone' => $entry->phone ?? '',
                    'email' => $entry->email ?? '',
                ];
            }
            return ['addresses' => $addresses];
        }, 'Failed to retrieve address book');
    }

    public function getServiceStructure(Config $config): WrapperResult
    {
        return $this->withApiClient($config, function ($apiClient) use ($config) {
            $serviceStructure = $apiClient->getServiceStructure();
            $data = [];
            if ($config->getRegion() === 'romania' && !empty($serviceStructure->packageTypes)) {
                $packageTypes = [];
                foreach ($serviceStructure->packageTypes as $pt) {
                    $vars = get_object_vars($pt);
                    $id = $vars['type'] ?? $vars['id'] ?? $vars['code'] ?? null;
                    $name = $vars['desc'] ?? $vars['name'] ?? $vars['label'] ?? '';
                    if ($id !== null) {
                        $packageTypes[] = ['id' => $id, 'name' => $name];
                    }
                }
                $data['package_types'] = $packageTypes;
            }
            return new WrapperResult(true, '', $data);
        }, 'Failed to retrieve service structure');
    }

    public function validateApiConnection(Config $config): bool
    {
        if (!$config->isComplete()) {
            return false;
        }
        $result = $this->withApiClient($config, function ($apiClient) use ($config) {
            if ($config->getRegion() === 'czechia') {
                $apiClient->getContactList();
            } else {
                $apiClient->getServiceStructure();
            }
            return new WrapperResult(true, '');
        }, 'API connection validation failed');
        return $result->isSuccess();
    }

    public function testWrapper(Config $config): WrapperResult
    {
        return $this->withApiClient($config, function ($apiClient) use ($config) {
            $serviceStructure = $apiClient->getServiceStructure();
            $data = [
                'message' => 'Wrapper connection test successful',
                'region' => ucfirst($config->getRegion()),
                'client_type' => basename(str_replace('\\', '/', get_class($apiClient))),
                'environment' => 'Test Environment'
            ];
            if (!empty($serviceStructure->services)) {
                $suppliers = [];
                $processedServices = [];
                foreach ($serviceStructure->services as $service) {
                    $supplierId = $service->supplier ?? 'Unknown';
                    $suppliers[$supplierId] = ($suppliers[$supplierId] ?? 0) + 1;
                    $processedServices[] = [
                        'id' => $service->externalId ?? 'N/A',
                        'name' => $service->name ?? 'Unknown Service',
                        'supplier' => $supplierId,
                        'to_point' => $service->toPoint ?? null,
                    ];
                }
                $data['stats'] = [
                    'total_services' => count($serviceStructure->services),
                    'unique_suppliers' => count($suppliers)
                ];
                $data['services'] = array_slice($processedServices, 0, 10);
            }
            return new WrapperResult(true, 'Connection test successful', $data);
        }, 'Connection test failed');
    }

    public function getWaybill(Config $config, string $orderId): WrapperResult
    {
        return $this->withApiClient($config, function ($apiClient) use ($orderId) {
            $response = $apiClient->getWayBill($orderId);
            return [
                'waybill' => $response->waybill,
                'type' => $response->type ?? 'pdf'
            ];
        });
    }

    public function getAvailableServices(Config $config, AlsendoRegionEntity $currentRegion): WrapperResult
    {
        return $this->withApiClient($config, function ($apiClient) use ($currentRegion) {
            $response = $apiClient->getServiceStructure();
            $processedServices = [];
            if (isset($response->services)) {
                $servicesData = [];
                foreach ($response->services as $service) {
                    $serviceData = [
                        'region_id' => $currentRegion->id,
                        'external_id' => $service->externalId,
                        'name' => $service->name,
                        'supplier' => $service->supplier ?? null,
                        'to_point' => $service->toPoint ?? null,
                        'comment' => $service->comment ?? null,
                        'point_to_point' => $service->pointToPoint ?? null,
                        'door_to_point' => $service->doorToPoint ?? null,
                        'point_to_door' => $service->pointToDoor ?? null,
                        'door_to_door' => $service->doorToDoor ?? null,
                        'logo_url' => $service->logoUrl ?? null,
                    ];
                    $servicesData[] = $serviceData;
                    $processedServices[] = $serviceData;
                }

                if ($currentRegion->code === 'poland') {
                    foreach ($servicesData as &$sd) {
                        $nameLower = strtolower($sd['name']);
                        $supplierLower = strtolower($sd['supplier']);
                        if (str_contains($nameLower, 'international') && $supplierLower === 'inpost') {
                            $sd['map_operator'] = 'INPOST_INTERNATIONAL';
                            continue;
                        }

                        $resolvedOperator = $this->mapWidgetService->resolveOperator($supplierLower);
                        if ($resolvedOperator) {
                            $sd['map_operator'] = $resolvedOperator;
                        }
                    }
                }

                if ($currentRegion->code === 'romania') {
                    foreach ($servicesData as &$sd) {
                        if ($sd['to_point'] === null || $sd['to_point'] === false) {
                            $slug = strtolower($sd['external_id'] ?? '');
                            $sd['to_point'] = (
                                str_ends_with($slug, '-locker')
                                || str_ends_with($slug, '-easybox')
                                || str_contains($slug, 'automat')
                                || str_ends_with($slug, '_locker')
                                || str_ends_with($slug, '_easybox')
                            );
                        }
                    }
                    unset($sd);
                    $processedServices = $servicesData;
                }

                if (!empty($servicesData)) {
                    $this->servicesRepository->syncServices($servicesData);
                }
            }
            return new WrapperResult(true, 'Services retrieved successfully', ['services' => $processedServices]);
        }, 'Failed to retrieve services');
    }

    public function getOrderValuation(Config $config, FullOrderDTO $dto): WrapperResult
    {
        try {
            return $this->withApiClient($config, function ($apiClient) use ($config, $dto) {
                $mappedOrderRequest = $this->getOrderRequestFromData($config, $dto);
                try {
                    $mappedOrderRequest = $this->prepareOrderRequestForSend($mappedOrderRequest, $dto);
                } catch (\Exception $e) {
                }

                if (isset($mappedOrderRequest->serviceId) && $config->getRegion() === 'romania') {
                    $mappedOrderRequest->serviceId = $this->normalizeEcoletServiceId($mappedOrderRequest->serviceId);
                }

                $response = $apiClient->getOrderValuation($mappedOrderRequest);
                return new WrapperResult(true, '', $response->valuations);
            });
        } catch (ValidationException $e) {
            $errors = $this->normalizeValidationErrors($e->getErrors());

            if (in_array($config->getRegion(), ['poland', 'romania'])) {
                $displayMessage = implode("<br>", array_map(
                    fn($k, $v) => $this->registry->language->get("field_$k") . ": $v",
                    array_keys($errors),
                    $errors
                ));
            } else {
                $displayMessage = sprintf(
                    '%s (%s)',
                    $e->getMessage(),
                    implode(', ', array_map(
                        fn($k, $v) => str_replace('_', ' ', $k) . ": $v",
                        array_keys($errors),
                        $errors
                    ))
                );
            }

            return new WrapperResult(
                false,
                $e->getMessage(),
                [],
                $displayMessage,
                $errors
            );
        }
    }

    public function getShipmentDetails(Config $config, array $shipmentIds): WrapperResult
    {
        return $this->withApiClient($config, function ($apiClient) use ($shipmentIds) {
            return $apiClient->getOrders($shipmentIds);
        });
    }

    public function sendOrder(Config $config, FullOrderDTO $dto, ?int $selectedServiceId = null): WrapperResult
    {
        try {
            return $this->withApiClient($config, function ($apiClient) use ($config, $dto, $selectedServiceId) {
                $mappedOrderRequest = $this->getOrderRequestFromData($config, $dto);
                $mappedOrderRequest = $this->prepareOrderRequestForSend($mappedOrderRequest, $dto);
                if ($selectedServiceId) {
                    $this->registry->load->model('extension/alsendo/module/service');
                    $serviceEntity = $this->registry->model_extension_alsendo_module_service->getServiceById($selectedServiceId);
                    $externalId = $serviceEntity->getExternalId();

                    if ($config->getRegion() === 'romania') {
                        $externalId = $this->normalizeEcoletServiceId($externalId);
                    }

                    $mappedOrderRequest->serviceId = $externalId;
                    $mappedOrderRequest->carrier = $externalId;
                    if (!$serviceEntity->isDeliveryToPoint()) {
                        $mappedOrderRequest->address->receiver->foreignAddressId = null;
                    }
                }

                $response = $apiClient->sendOrder($mappedOrderRequest);

                $waybillNumber = $response->waybillNumber ?? null;
                $trackingUrl = $response->trackingUrl;
                if ($config->getRegion() === 'romania' && $response->id) {
                    $orderResponse = $apiClient->getOrder($response->id);
                    if (isset($orderResponse->awb)) {
                        $waybillNumber = $orderResponse->awb;
                        $trackingUrl = 'https://panel.ecolet.ro/ro/track/' . $orderResponse->awb;
                    }
                }

                return [
                    'id' => $response->id,
                    'status' => $response->status,
                    'service_id' => $response->serviceId,
                    'service_name' => $response->serviceName,
                    'waybill_number' => $waybillNumber,
                    'tracking_url' => $trackingUrl,
                    'created' => $response->created ? [
                        'date' => $response->created->format('Y-m-d H:i:s.u'),
                        'timezone' => $response->created->getTimezone()->getName(),
                    ] : null,
                ];
            });
        } catch (ValidationException $e) {
            return new WrapperResult(false, $e->getMessage(), [], $e->getMessage(), $this->normalizeValidationErrors($e->getErrors()));
        }
    }

    public function cancelOrder(Config $config, array $alsendoOrderIds): WrapperResult
    {
        if (empty($alsendoOrderIds)) {
            return new WrapperResult(false, 'No orders to cancel', [], 'No orders to cancel');
        }
        try {
            return $this->withApiClient($config, function ($apiClient) use ($alsendoOrderIds) {
                foreach ($alsendoOrderIds as $orderId) {
                    $apiClient->cancelOrder((string) $orderId);
                }
                return new WrapperResult(true, 'Orders cancelled successfully');
            });
        } catch (ValidationException $e) {
            $errors = $e->getErrors();
            $displayError = $e->getMessage();
            foreach ($errors as $messages) {
                $msg = is_array($messages) ? ($messages[0] ?? null) : $messages;
                if ($msg !== null && $msg !== '') {
                    $displayError = $msg;
                    break;
                }
            }
            return new WrapperResult(
                false,
                $e->getMessage(),
                [],
                $displayError,
                $this->normalizeValidationErrors($errors)
            );
        }
    }

    public function getOrderRequestFromData(Config $config, FullOrderDTO $dto): OrderRequest
    {
        if (!$dto->orderDetails) {
            throw new \Exception('Order details are required');
        }

        $orderDetailsData = $dto->getOrderDetailsDataForWrapper();
        $openCartOrder = $dto->openCartOrder;

        $hasSenderAddressId = false;
        $sender = new Contact();
            if ($config->getRegion() === 'czechia' && !empty($orderDetailsData['sender']['external_id'])) {
                $sender->id = (int) $orderDetailsData['sender']['external_id'];
                $hasSenderAddressId = true;
            }

            if (!$hasSenderAddressId) {
                $sender->countryCode = $orderDetailsData['sender']['country'];
                $sender->country = $orderDetailsData['sender']['country'];
                $sender->name = $orderDetailsData['sender']['address_type'] === 'company'
                    ? $orderDetailsData['sender']['company_name']
                    : $orderDetailsData['sender']['full_name'];
                $sender->line1 = sprintf(
                    '%s %s%s',
                    $orderDetailsData['sender']['street'],
                    $orderDetailsData['sender']['house_number'],
                    (!empty($orderDetailsData['sender']['apartment_number']) ? sprintf(' / %s', $orderDetailsData['sender']['apartment_number']) : '')
                );
                $sender->line2 = '';
                $sender->postalCode = $orderDetailsData['sender']['postal_code'];
                $sender->city = $orderDetailsData['sender']['city'];
                $sender->isResidential = $orderDetailsData['sender']['address_type'] === 'company' ? 0 : 1;
                $sender->contactPerson = $orderDetailsData['sender']['full_name'];
                $sender->email = $orderDetailsData['sender']['email'];
                $sender->phone = $orderDetailsData['sender']['phone_number'];
                $sender->company = $orderDetailsData['sender']['address_type'] === 'company'
                    ? $orderDetailsData['sender']['company_name']
                    : null;

                if ($config->getRegion() === 'romania') {
                    $companyName = $orderDetailsData['sender']['company_name'] ?? '';
                    if ($orderDetailsData['sender']['address_type'] === 'company' && $companyName !== '') {
                        $sender->company = $companyName;
                    } else {
                        unset($sender->company);
                    }
                    $sender->hasMapPoint = false;
                    $sender->streetName = $orderDetailsData['sender']['street'] ?? '';
                    $sender->streetNumber = $orderDetailsData['sender']['house_number'] ?? '';
                    if (!empty($orderDetailsData['sender']['block']) || !empty($orderDetailsData['sender']['entrance'])
                        || !empty($orderDetailsData['sender']['floor']) || !empty($orderDetailsData['sender']['apartment_number'])) {
                        $sender->block = $orderDetailsData['sender']['block'] ?? '';
                        $sender->entrance = $orderDetailsData['sender']['entrance'] ?? '';
                        $sender->floor = $orderDetailsData['sender']['floor'] ?? '';
                        $sender->flat = $orderDetailsData['sender']['apartment_number'] ?? '';
                    }
                }
            }

        $receiver = new Contact();
            $receiver->countryCode = Country::getIsoCode($openCartOrder['shipping_country']);
            $receiver->country = Country::getIsoCode($openCartOrder['shipping_country']);
            $receiver->name = ($openCartOrder['shipping_firstname'] ?? '') . ' ' . ($openCartOrder['shipping_lastname'] ?? '');
            if ($config->getRegion() === 'romania') {
                $receiver->streetName = $orderDetailsData['shipping_address']['street'];
                $receiver->streetNumber = $orderDetailsData['shipping_address']['house_number'];
                $receiver->block = $orderDetailsData['shipping_address']['block'];
                $receiver->floor = $orderDetailsData['shipping_address']['floor'];
                $receiver->entrance = $orderDetailsData['shipping_address']['entrance'];
                $receiver->flat = $orderDetailsData['shipping_address']['apartment_number'];
            } else {
                $receiver->line1 = ($openCartOrder['shipping_address_1'] ?? '')
                    . (!empty($openCartOrder['shipping_address_2'] ?? '') ? ' / ' . $openCartOrder['shipping_address_2'] : '');
                $receiver->line2 = '';
            }
            $receiver->postalCode = $openCartOrder['shipping_postcode'] ?? '';
            $receiver->stateCode = 'YY';
            $receiver->city = $openCartOrder['shipping_city'] ?? '';
            $receiver->isResidential = (!empty($openCartOrder['shipping_company'])) ? 0 : 1;
            $receiver->contactPerson = $receiver->name;
            $receiver->email = $openCartOrder['email'] ?? '';
            $receiver->phone = $openCartOrder['telephone'] ?? '';

        if ($config->getRegion() === 'romania') {
            $shippingCompany = $openCartOrder['shipping_company'] ?? '';
            if (!empty($shippingCompany)) {
                $receiver->company = $shippingCompany;
            } else {
                unset($receiver->company);
            }
        }

        if ($dto->orderPickupPoint) {
            $receiver->mapPointId = $dto->orderPickupPoint->getCode();
        }


        $address = new Address($sender, $receiver);

        $emptyNotificationDetail = new NotificationDetail(0, 0, 0, 0);
        $notification = new Notification($emptyNotificationDetail, $emptyNotificationDetail, $emptyNotificationDetail, $emptyNotificationDetail);

        $cod = null;

        if (!empty($orderDetailsData['package']['cod_value'])) {
            $cod = new Cod();
            $cod->amount = number_format((float)$orderDetailsData['package']['cod_value'], 2, '.', '');
            $shippingCountryName = Country::getIsoCode($openCartOrder['shipping_country'] ?? '');
            $isZaslatToNonCz = ($config->getRegion() === 'czechia' && !in_array(strtoupper((string)$shippingCountryName), ['Czech Republic', 'CZ', 'CZE', 'Czechia', 'Czech', 'Česko', 'Česká republika']));

            if ($isZaslatToNonCz) {
                $cod->bankaccount = !empty($orderDetailsData['sender']['additional_bank_account_number'])
                    ? $orderDetailsData['sender']['additional_bank_account_number']
                    : ($orderDetailsData['sender']['bank_account_number'] ?? '');
            } else {
                $cod->bankaccount = $orderDetailsData['sender']['bank_account_number'] ?? '';
                if (!empty($orderDetailsData['sender']['bank_code'])) {
                    $cod->bankCode = $orderDetailsData['sender']['bank_code'];
                }
            }
        }

        $pickup = new Pickup();

        $pickupType = $orderDetailsData['package']['pickup_type'] ?? 'COURIER';
        if ($config->getRegion() === 'romania') {
            $pickupType = strtoupper($pickupType);
            if (!in_array($pickupType, ['COURIER', 'SELF'])) {
                $pickupType = 'COURIER';
            }
            $pickupType = strtolower($pickupType);
        }
        $pickup->type = $pickupType;

        if ($config->getRegion() === 'czechia' && !empty($orderDetailsData['package']['ship_via_pickup_point']) && (bool) $orderDetailsData['package']['ship_via_pickup_point'] === true) {
            $pickup->pickupBranch = '1';
        }

        if (in_array(strtolower($pickup->type), ['courier', 'ondemand'])) {
            $pickup->date = $orderDetailsData['shipment']['preferred_pickup_date'] ?? null;
            $pickup->hoursFrom = $orderDetailsData['shipment']['preferred_pickup_hour_from'] ?? null;
            $pickup->hoursTo = $orderDetailsData['shipment']['preferred_pickup_hour_to'] ?? null;
        }

        $orderRequest = new OrderRequest();

        $selectedServiceId = $dto->getSelectedServiceId();

        if ($selectedServiceId) {
            $this->registry->load->model('extension/alsendo/module/service');
            $serviceEntity = $this->registry->model_extension_alsendo_module_service->getServiceById($selectedServiceId);

            $orderRequest->serviceId = $serviceEntity->getExternalId();

            if ($config->getRegion() === 'czechia') {
                $orderRequest->carrier = $serviceEntity->getSupplier() ?? null;
            }
        }

        $orderRequest->address = $address;
        $orderRequest->option = [
//            '31' => 1, // powiadomienie sms,
//            '11' => 1, // rod
//            '19' => 1, // dostawa w sobotę,
//            '25' => 1, // dostawa w godzinach,
//            '58' => 1, // ostrożnie
        ];

        $orderRequest->notification = $notification;
        $defaultShipmentValue = ($config->getRegion() === 'poland') ? 5000 : 0;
        $orderRequest->shipmentValue = !empty($orderDetailsData['package']['declared_value'])
            ? (int) round((float)$orderDetailsData['package']['declared_value'] * 100)
            : $defaultShipmentValue;

        if ($cod) {
            $orderRequest->cod = $cod;

            if ($config->getRegion() === 'poland') {
                $orderRequest->cod->amount = $orderRequest->cod->amount * 100;
            }
        }
        $orderRequest->pickup = $pickup;

        $packages = $orderDetailsData['package']['packages'];

        $shipments = [];
        foreach ($packages as $package) {
            if (in_array($config->getRegion(), ['czechia', 'romania'])) {
                $shipment = new Shipment();
                $shipment->dimension1 = $package['package_length'] ?? null;
                $shipment->dimension2 = $package['package_width'] ?? null;
                $shipment->dimension3 = $package['package_height'] ?? null;
                $shipment->weight = $package['package_weight'] ?? null;
                $shipment->isNstd = ($package['package_shipment_packaging'] ?? 'standard') === 'non-standard' ? 1 : 0;

                if ($config->getRegion() === 'romania') {
                    $typeCode = $package['package_shipment_packaging'] ?? 'package';
                    if (!in_array($typeCode, ['package', 'envelope', 'pallet'], true)) {
                        $typeCode = 'package';
                    }
                    $shipment->shipmentTypeCode = $typeCode;
                    $shipment->isNstd = 0;
                    $shipment->shape = 'standard';
                    $rawShipmentContent = $orderDetailsData['package']['package_content'] ?? '';
                    $shipment->content = (strlen($rawShipmentContent) >= 3) ? 'Colet: ' . $rawShipmentContent : 'Colet OpenCart';
                    $shipment->declaredValue = !empty($orderDetailsData['package']['declared_value'])
                        ? (string)$orderDetailsData['package']['declared_value'] : 0;
                }

                if ($config->getRegion() === 'czechia' && !empty($orderDetailsData['package']['ship_via_pickup_point'])) {
                    $shipment->pickupBranch = 1;
                }

                if ($config->getRegion() === 'romania') {
                    $isNonStandard = (bool) ($orderDetailsData['package']['packages'][0]['non_standard'] ?? false);

                    if ($isNonStandard) {
                        $shipment->shape = 'nonstandard';
                    }
                }

                $shipments[] = $shipment;

                continue;
            }

            $shipment =  [
                'dimension1'        => $package['package_length'] ?? null,
                'dimension2'        => $package['package_width'] ?? null,
                'dimension3'        => $package['package_height'] ?? null,
                'weight'            => $package['package_weight'] ?? null,
                'is_nstd'           => 0,
            ];

            if ($config->getRegion() === 'poland') {
                $shipment['shipment_type_code'] = $package['package_shipment_packaging'] ?? 'PACZKA';
            }

            $shipments[] = $shipment;
        }

        $orderRequest->shipment = $shipments;

        $orderRequest->comment = '';
        $rawContent = $orderDetailsData['package']['package_content'] ?? '';
        if ($config->getRegion() === 'romania') {
            $orderRequest->content = (strlen($rawContent) >= 3) ? 'Colet: ' . $rawContent : 'Colet OpenCart';
        } else {
            $orderRequest->content = $rawContent;
        }

        $orderRequest->currency = match ($config->getRegion()) {
            'czechia' => 'czk',
            'romania' => 'rom',
            default   => 'pln',
        };

        $orderRequest->source = 'opencart';

        if ($config->getRegion() === 'czechia') {
            $orderRequest->pickup->hoursFrom = null;
            $orderRequest->pickup->hoursTo = null;
            $pickupType = strtoupper((string)($orderDetailsData['package']['pickup_type'] ?? ''));
            if ($pickupType === 'ONDEMAND') {
                $orderRequest->pickupRequest = false;
            } elseif (!empty($orderDetailsData['package']['pickup_request'])) {
                $orderRequest->pickupRequest = (bool) $orderDetailsData['package']['pickup_request'];
            }
        }

        if ($config->getRegion() === 'romania') {
            if (empty($orderRequest->serviceId)) {
                $orderRequest->serviceId = '';
            }
        }

        return $orderRequest;
    }

    private function prepareOrderRequestForSend(OrderRequest $orderRequest, FullOrderDTO $data): OrderRequest
    {
        $preparedOrderRequest = clone $orderRequest;

        $orderDetails = $data->getOrderDetailsDataForWrapper();
        if (!$orderDetails) {
            return $orderRequest;
        }

        $shipmentSelectedService = $orderDetails['shipment']['shipping_method_id'] ?? null;

        if ($shipmentSelectedService) {
            if ($data->orderPickupPoint) {
                $code = $data->orderPickupPoint->getCode();
                $preparedOrderRequest->address->receiver->foreignAddressId = $code;
                $preparedOrderRequest->address->receiver->hasMapPoint = true;
                $preparedOrderRequest->address->receiver->mapPointId = is_numeric($code) ? (int)$code : $code;
            }

            if (strtolower($data->getSenderPackagePickupType()) === 'self') {
                $this->applySenderMerchantPickupPoint($preparedOrderRequest, $data);
            }
        }

        return $preparedOrderRequest;
    }

    private function applySenderMerchantPickupPoint(OrderRequest $orderRequest, FullOrderDTO $dto): bool
    {
        $orderDetailsData = $dto->getOrderDetailsDataForWrapper();
        if (!$orderDetailsData) {
            return false;
        }
        $merchantPickupPoint = $orderDetailsData['shipment']['shipment_pickup_point'] ?? null;
        if (empty($merchantPickupPoint)) {
            $merchantPickupPoint = $this->getDefaultMerchantPickupPointFromConfig($dto);
        }
        if (!$merchantPickupPoint || empty($merchantPickupPoint['code'])) {
            return false;
        }
        $point = is_array($merchantPickupPoint) ? $merchantPickupPoint : json_decode(htmlspecialchars_decode($merchantPickupPoint), true);
        if (empty($point['code'])) {
            return false;
        }
        $code = $point['code'];
        $orderRequest->address->sender->foreignAddressId = $code;
        $orderRequest->address->sender->hasMapPoint = true;
        $orderRequest->address->sender->mapPointId = is_numeric($code) ? (int)$code : $code;
        return true;
    }

    private function getDefaultMerchantPickupPointFromConfig(FullOrderDTO $dto): ?array
    {
        $externalId = $dto->getSelectedServiceExternalId();
        if (empty($externalId)) {
            return null;
        }
        $selectedServices = $this->servicesRepository->getSelectedServices();
        $supplier = null;
        foreach ($selectedServices as $svc) {
            if ((string)$svc->getExternalId() === (string)$externalId) {
                $supplier = strtolower($svc->getSupplier() ?? '');
                break;
            }
        }
        if ($supplier === null || $supplier === '') {
            return null;
        }
        $settings = $this->additionalSettingsDataProvider->getData()['additional_settings'] ?? [];
        $key = $supplier . '_default_point';
        $point = $settings[$key] ?? null;
        return is_array($point) && !empty($point['code']) ? $point : null;
    }

    private function refreshEcoletTokenIfNeeded(Config $config): void
    {
        $apiConfig = $config->getWrapperConfig();
        $expiresAt = (int)($apiConfig['ro_client_oauth_expires_at'] ?? 0);
        $refreshToken = $apiConfig['ro_client_oauth_refresh_token'] ?? '';

        if (empty($refreshToken) || $expiresAt === 0) {
            return;
        }

        if (time() <= ($expiresAt - 60)) {
            return;
        }

        try {
            require_once($this->extensionDir . 'wrapper/vendor/autoload.php');

            $clientId = $config->getRoClientId();
            $clientSecret = $config->getRoClientSecret();
            if (empty($clientId) || empty($clientSecret)) {
                return;
            }

            $isTestMode = $config->getRoSandbox();
            $oauthConfig = [
                'client_id' => $clientId,
                'client_secret' => $clientSecret,
                'redirect_uri' => 'https://google.com',
            ];

            $oauthClient = new \Alsendo\AlsendoWrapper\Api\Ecolet\EcoletOAuthClient($oauthConfig, $isTestMode);
            $token = $oauthClient->refreshAccessToken($refreshToken);

            if ($token && $this->configRepository) {
                $this->configRepository->saveApiConfig([
                    'ro_client_oauth_access_token' => $token->getAccessToken(),
                    'ro_client_oauth_refresh_token' => $token->refreshToken,
                    'ro_client_oauth_expires_at' => (string)$token->expiresAt,
                ]);

                $updatedApiConfig = $config->getWrapperConfig();
                $updatedApiConfig['ro_client_oauth_access_token'] = $token->getAccessToken();
                $updatedApiConfig['ro_client_oauth_refresh_token'] = $token->refreshToken;
                $updatedApiConfig['ro_client_oauth_expires_at'] = (string)$token->expiresAt;
                $config->setApiConfig($updatedApiConfig);
            }
        } catch (\Throwable $e) {
        }
    }

    private function normalizeEcoletServiceId(string $serviceId): string
    {
        $allServices = $this->servicesRepository->getSelectedServices();
        $knownIds = array_map(fn($s) => $s->getExternalId(), $allServices);

        if (in_array($serviceId, $knownIds, true)) {
            return $serviceId;
        }

        $bestMatch = $serviceId;
        $bestLen = 0;
        foreach ($knownIds as $knownId) {
            if (str_starts_with($serviceId, $knownId) && strlen($knownId) > $bestLen) {
                $bestMatch = $knownId;
                $bestLen = strlen($knownId);
            }
        }
        return $bestMatch;
    }

    public function selectCheapestService(array $valuationArray, string $region, bool $hasMap = false): ?array
    {
        $allServices = $this->servicesRepository->getSelectedServices();
        $toPointMap = [];
        foreach ($allServices as $svc) {
            $toPointMap[(string)$svc->getExternalId()] = $svc->isDeliveryToPoint();
        }

        $cheapest = null;
        $cheapestPrice = PHP_FLOAT_MAX;

        foreach ($valuationArray as $sid => $service) {
            $priceGross = (float)($service['priceTable']['priceGross'] ?? 0);
            if ($priceGross <= 0) continue;

            if ($region === 'poland') $priceGross /= 100;

            $effectiveId = $sid;
            if ($region === 'czechia' && !empty($service['carrier'])) {
                $effectiveId = $service['carrier'];
            }

            if (!empty($toPointMap)) {
                $isToPoint = $toPointMap[(string)$sid] ?? $toPointMap[(string)$effectiveId] ?? null;
                if ($hasMap && $isToPoint === false) continue;
                if (!$hasMap && $isToPoint === true) continue;
            }

            if ($priceGross < $cheapestPrice) {
                $cheapestPrice = $priceGross;
                $cheapest = [
                    'service_id' => $effectiveId,
                    'price_gross' => $cheapestPrice,
                    'service_name' => $service['serviceName'] ?? $service['name'] ?? ('Service ' . $effectiveId),
                ];
            }
        }

        if (!$cheapest) {
            $cheapestPrice = PHP_FLOAT_MAX;
            foreach ($valuationArray as $sid => $service) {
                $priceGross = (float)($service['priceTable']['priceGross'] ?? 0);
                if ($priceGross <= 0) continue;
                if ($region === 'poland') $priceGross /= 100;
                $effectiveId = $sid;
                if ($region === 'czechia' && !empty($service['carrier'])) {
                    $effectiveId = $service['carrier'];
                }
                if ($priceGross < $cheapestPrice) {
                    $cheapestPrice = $priceGross;
                    $cheapest = [
                        'service_id' => $effectiveId,
                        'price_gross' => $cheapestPrice,
                        'service_name' => $service['serviceName'] ?? $service['name'] ?? ('Service ' . $effectiveId),
                    ];
                }
            }
        }

        return $cheapest;
    }

    private function normalizeValidationErrors(array $apiErrors): array
    {
        $map = [
            'address.receiver.phone' => 'shipping_phone_number',
            'address.receiver.email' => 'shipping_email',
            'address.receiver.name' => 'shipping_first_name',
            'address.receiver.countryCode' => 'shipping_country',
            'address.receiver.line1' => 'shipping_address_one',
            'address.receiver.postalCode' => 'shipping_postal_code',
            'address.receiver.city' => 'shipping_city',
            'address.sender.phone' => 'sender_phone_number',
            'address.sender.email' => 'sender_email',
            'address.sender.name' => 'sender_full_name',
            'address.sender.countryCode' => 'sender_country',
            'address.sender.line1' => 'sender_street',
            'address.sender.postalCode' => 'sender_postal_code',
            'address.sender.city' => 'sender_city',
            'pickup.date' => 'shipment_preferred_pickup_date',
            'pickup.hoursFrom' => 'shipment_preferred_pickup_hour_from',
            'pickup.hoursTo' => 'shipment_preferred_pickup_hour_to',
            'pickup.type' => 'sender_address_type',
        ];
        $normalized = [];
        foreach ($apiErrors as $path => &$messages) {
            if (is_array($messages)) {
                foreach ($messages as &$message) {
                    $message = $this->registry->language->get($message);
                }
            } elseif (is_string($messages)) {
                $message = $this->registry->language->get($messages);
            } else {
                continue;
            }

            $formField = $map[$path] ?? str_replace('.', '_', $path);
            $msg = is_array($messages) ? (reset($messages) ?: '') : (string) $messages;
            $normalized[$formField] = $msg;
        }

        return $normalized;
    }
} 