<?php
declare(strict_types=1);

namespace App\Modules\Shipments;

use App\Core\Exceptions\IntegrationConfigException;
use App\Core\Exceptions\ShipmentException;
use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\CompanySettingsRepository;
use App\Modules\Settings\PolkurierApiClient;
use App\Modules\Settings\PolkurierIntegrationRepository;
use Throwable;

/**
 * polkurier.pl ShipmentProvider (Phase 128).
 *
 * Tworzy paczki, pobiera etykiety i wystawia dostepne uslugi przewoznicze przez API polkurier.
 * Payload zgodny z SDK polkurier-sdk (zweryfikowany na Sender/Recipient/Pack/Pickup/COD entity klasach).
 */
final class PolkurierShipmentService implements ShipmentProviderInterface
{
    /** @var array<int, array<string, mixed>>|null */
    private ?array $carriersCache = null;

    public function __construct(
        private readonly PolkurierIntegrationRepository $integrationRepository,
        private readonly PolkurierApiClient $apiClient,
        private readonly ShipmentPackageRepository $packages,
        private readonly CompanySettingsRepository $companySettings,
        private readonly OrdersRepository $ordersRepository
    ) {
    }

    public function code(): string
    {
        return 'polkurier';
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function getDeliveryServices(): array
    {
        if ($this->carriersCache !== null) {
            return $this->carriersCache;
        }

        $credentials = $this->integrationRepository->getCredentials();
        if ($credentials === null) {
            return $this->carriersCache = [];
        }

        try {
            $carriers = $this->apiClient->getAvailableCarriers(
                $credentials['login'],
                $credentials['api_token']
            );
        } catch (Throwable) {
            return $this->carriersCache = [];
        }

        // Normalizacja: ujednolicony shape `{id, name, supports_pickup_point, foreign_shipments}`
        $normalized = [];
        foreach ($carriers as $carrier) {
            if (!is_array($carrier)) {
                continue;
            }
            $code = trim((string) ($carrier['servicecode'] ?? $carrier['code'] ?? ''));
            $name = trim((string) ($carrier['name'] ?? $code));
            if ($code === '') {
                continue;
            }
            $supportsPoint = $this->detectPickupPointSupport($code, $carrier);
            $normalized[] = [
                'id' => $code,
                'name' => $name !== '' ? $name : $code,
                'supports_pickup_point' => $supportsPoint,
                'point_courier' => $supportsPoint ? $this->resolvePointCourierKey($code) : null,
                'foreign_shipments' => !empty($carrier['foreign_shipments']),
                'raw' => $carrier,
            ];
        }

        usort($normalized, static fn ($a, $b) => strcasecmp((string) $a['name'], (string) $b['name']));
        return $this->carriersCache = $normalized;
    }

    /**
     * @param array<string, mixed> $formData
     * @return array<string, mixed>
     */
    public function createShipment(int $orderId, array $formData): array
    {
        $order = $this->ordersRepository->findDetails($orderId);
        if ($order === null) {
            throw new ShipmentException('Zamowienie nie znalezione.');
        }

        $credentials = $this->requireCredentials();
        $sender = $this->companySettings->getSenderAddress();
        $this->validateSender($sender);

        $courierCode = strtoupper(trim((string) (
            $formData['service_code']
            ?? $formData['delivery_method_id']
            ?? ''
        )));
        if ($courierCode === '') {
            throw new ShipmentException('Nie wybrano uslugi polkurier (servicecode).');
        }

        $orderData = is_array($order['order'] ?? null) ? $order['order'] : [];
        $sourceOrderId = trim((string) ($orderData['source_order_id'] ?? ($orderData['external_order_number'] ?? '')));
        $description = 'orderPRO ' . ($sourceOrderId !== '' ? $sourceOrderId : (string) $orderId);

        $weightKg = max(0.001, (float) ($formData['weight_kg'] ?? 1.0));
        $lengthCm = max(1.0, (float) ($formData['length_cm'] ?? 25.0));
        $widthCm = max(1.0, (float) ($formData['width_cm'] ?? 20.0));
        $heightCm = max(1.0, (float) ($formData['height_cm'] ?? 8.0));
        $shipmentType = $this->normalizeShipmentType((string) ($formData['package_type'] ?? 'BOX'));

        $receiverPointId = trim((string) ($formData['receiver_point_id'] ?? ''));
        $recipient = $this->buildRecipient($order, $formData, $receiverPointId);
        $senderPayload = $this->buildSenderPayload($sender);

        $packs = [[
            'length' => (int) round($lengthCm),
            'width' => (int) round($widthCm),
            'height' => (int) round($heightCm),
            'weight' => round($weightKg, 3),
            'amount' => 1,
            'type' => $shipmentType,
        ]];

        $pickup = $this->buildPickup($formData);

        $apiPayload = [
            'shipmenttype' => $shipmentType,
            'courier' => $courierCode,
            'description' => $description,
            'sender' => $senderPayload,
            'recipient' => $recipient,
            'packs' => $packs,
            'pickup' => $pickup,
        ];

        $insurance = max(0.0, (float) ($formData['insurance_amount'] ?? 0));
        if ($insurance > 0) {
            $apiPayload['insurance'] = round($insurance, 2);
        }

        $cod = max(0.0, (float) ($formData['cod_amount'] ?? 0));
        if ($cod > 0) {
            $companySettings = $this->companySettings->getSettings();
            $bankAccount = preg_replace('/[^0-9]/', '', (string) ($companySettings['bank_account'] ?? '')) ?? '';
            if ($bankAccount === '') {
                throw new ShipmentException('Przesylka COD wymaga numeru konta bankowego. Uzupelnij go w Ustawienia > Firma.');
            }
            $apiPayload['COD'] = [
                'codtype' => 'transfer',
                'codamount' => round($cod, 2),
                'codbankaccount' => $bankAccount,
                'return_cod' => 'transfer',
            ];
        }

        $carrierLabel = $this->resolveCarrierLabel($courierCode);
        $labelFormat = strtoupper(trim((string) ($formData['label_format'] ?? $credentials['default_label_format'] ?? 'PDF')));
        if (!in_array($labelFormat, ['PDF', 'ZPL', 'EPL'], true)) {
            $labelFormat = 'PDF';
        }

        $packageId = $this->packages->create([
            'order_id' => $orderId,
            'provider' => 'polkurier',
            'delivery_method_id' => $courierCode,
            'credentials_id' => null,
            'command_id' => null,
            'status' => 'pending',
            'carrier_id' => $carrierLabel,
            'package_type' => $shipmentType,
            'weight_kg' => $weightKg,
            'length_cm' => $lengthCm,
            'width_cm' => $widthCm,
            'height_cm' => $heightCm,
            'insurance_amount' => $insurance > 0 ? $insurance : null,
            'insurance_currency' => 'PLN',
            'cod_amount' => $cod > 0 ? $cod : null,
            'cod_currency' => $cod > 0 ? 'PLN' : null,
            'label_format' => $labelFormat,
            'receiver_point_id' => $receiverPointId,
            'sender_point_id' => trim((string) ($formData['sender_point_id'] ?? '')),
            'reference_number' => $sourceOrderId !== '' ? $sourceOrderId : (string) $orderId,
            'payload_json' => $apiPayload,
        ]);

        try {
            $response = $this->apiClient->createShipment(
                $credentials['login'],
                $credentials['api_token'],
                $apiPayload
            );
        } catch (Throwable $exception) {
            $message = 'polkurier create_order: ' . $exception->getMessage();
            $this->packages->update($packageId, [
                'status' => 'error',
                'error_message' => $message,
            ]);
            throw new ShipmentException($message, 0, $exception);
        }

        $orderno = $this->extractOrderNumber($response);
        $tracking = $this->extractTrackingNumber($response, $orderno);

        if ($orderno === '') {
            // Diagnostyka — polkurier zwrocil odpowiedz ale bez rozpoznawalnego pola order number.
            // Zapisujemy fragment odpowiedzi do error_message zeby operator/dev zobaczyl shape.
            $dump = json_encode($response, JSON_UNESCAPED_UNICODE);
            if (is_string($dump)) {
                $dump = substr($dump, 0, 400);
            } else {
                $dump = '(brak czytelnej odpowiedzi)';
            }
            $this->packages->update($packageId, [
                'status' => 'pending',
                'error_message' => 'polkurier: utworzono w API ale nie znaleziono order number w odpowiedzi. Fragment: ' . $dump,
            ]);
        } else {
            $this->packages->update($packageId, [
                'status' => 'created',
                'shipment_id' => $orderno,
                'command_id' => $orderno,
                'tracking_number' => $tracking !== '' ? $tracking : null,
            ]);
        }

        // Sprobuj odrazu pobrac etykiete (synchronously). Niekrytyczne — operator moze pobrac pozniej.
        if ($orderno !== '') {
            try {
                $this->downloadLabel($packageId, $this->resolveStorageRoot());
            } catch (Throwable) {
                // ignore — etykieta jeszcze nie gotowa po stronie polkuriera, operator klikni "Pobierz" pozniej
            }
        }

        return [
            'package_id' => $packageId,
            'command_id' => $orderno !== '' ? $orderno : null,
            'tracking_number' => $tracking,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function checkCreationStatus(int $packageId): array
    {
        $package = $this->packages->findById($packageId);
        if ($package === null) {
            throw new ShipmentException('Paczka nie znaleziona.');
        }

        $orderno = trim((string) ($package['shipment_id'] ?? $package['command_id'] ?? ''));
        if ($orderno === '') {
            return ['status' => 'in_progress'];
        }

        $credentials = $this->requireCredentials();
        try {
            $statusResp = $this->apiClient->getStatus(
                $credentials['login'],
                $credentials['api_token'],
                $orderno
            );
        } catch (Throwable) {
            return [
                'status' => 'created',
                'shipment_id' => $orderno,
                'tracking_number' => trim((string) ($package['tracking_number'] ?? '')),
            ];
        }

        return [
            'status' => 'created',
            'shipment_id' => $orderno,
            'tracking_number' => trim((string) ($package['tracking_number'] ?? '')),
            'raw_status' => trim((string) ($statusResp['status'] ?? $statusResp['statuscode'] ?? '')),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function downloadLabel(int $packageId, string $storagePath): array
    {
        $package = $this->packages->findById($packageId);
        if ($package === null) {
            throw new ShipmentException('Paczka nie znaleziona.');
        }

        $orderno = trim((string) ($package['shipment_id'] ?? $package['command_id'] ?? ''));
        if ($orderno === '') {
            throw new ShipmentException('Przesylka polkurier nie zostala jeszcze utworzona.');
        }

        $credentials = $this->requireCredentials();
        $format = strtoupper(trim((string) ($package['label_format'] ?? 'PDF')));
        if (!in_array($format, ['PDF', 'ZPL', 'EPL'], true)) {
            $format = 'PDF';
        }

        try {
            $response = $this->apiClient->getLabel(
                $credentials['login'],
                $credentials['api_token'],
                $orderno
            );
        } catch (Throwable $exception) {
            throw new ShipmentException('polkurier get_label: ' . $exception->getMessage(), 0, $exception);
        }

        $base64 = $this->extractLabelBase64($response);
        if ($base64 === '') {
            throw new ShipmentException('polkurier nie zwrocil danych etykiety.');
        }

        $binary = base64_decode($base64, true);
        if (!is_string($binary) || $binary === '') {
            throw new ShipmentException('Nie mozna odczytac etykiety polkurier.');
        }

        $extension = strtolower($format);
        $dir = rtrim($storagePath, '/\\') . '/labels';
        if (!is_dir($dir)) {
            mkdir($dir, 0775, true);
        }

        $filename = 'polkurier_' . $packageId . '_' . preg_replace('/[^A-Za-z0-9_-]/', '', $orderno) . '.' . $extension;
        $filePath = $dir . '/' . $filename;
        file_put_contents($filePath, $binary);

        $this->packages->update($packageId, [
            'status' => 'label_ready',
            'label_path' => 'labels/' . $filename,
        ]);

        return [
            'label_path' => 'labels/' . $filename,
            'full_path' => $filePath,
        ];
    }

    /**
     * polkurier get_label zwraca base64 PDF pod kluczem 'file' (zweryfikowane w SDK GetLabel.php).
     */
    private function extractLabelBase64(mixed $response): string
    {
        if (is_string($response)) {
            return trim($response);
        }
        if (is_array($response)) {
            foreach (['file', 'label', 'pdf', 'data', 'content', 'zpl', 'epl'] as $key) {
                if (isset($response[$key]) && is_string($response[$key])) {
                    $candidate = trim((string) $response[$key]);
                    if ($candidate !== '') {
                        return $candidate;
                    }
                }
            }
            if (isset($response[0]) && is_array($response[0])) {
                return $this->extractLabelBase64($response[0]);
            }
        }
        return '';
    }

    /**
     * polkurier create_order zwraca Order entity. Numer zamowienia jest w polu 'number'
     * (zmapowane z setNumber() w SDK Order.php). Fallbacki dla mozliwych wariantow.
     *
     * @param array<string, mixed> $response
     */
    private function extractOrderNumber(array $response): string
    {
        // Czasami polkurier opakowuje w {order: {...}} lub zwraca liste
        if (isset($response['order']) && is_array($response['order'])) {
            $inner = $this->extractOrderNumber($response['order']);
            if ($inner !== '') {
                return $inner;
            }
        }
        if (isset($response[0]) && is_array($response[0])) {
            $inner = $this->extractOrderNumber($response[0]);
            if ($inner !== '') {
                return $inner;
            }
        }

        foreach (['number', 'orderno', 'order_no', 'order_number', 'order_id', 'id'] as $key) {
            $value = trim((string) ($response[$key] ?? ''));
            if ($value !== '') {
                return $value;
            }
        }
        return '';
    }

    /**
     * Waybill polkurier zazwyczaj w `waybills[0].number` (OrderWaybill entity).
     * Fallbacki dla starszych wariantow odpowiedzi.
     *
     * @param array<string, mixed> $response
     */
    private function extractTrackingNumber(array $response, string $orderno): string
    {
        if (isset($response['order']) && is_array($response['order'])) {
            $inner = $this->extractTrackingNumber($response['order'], $orderno);
            if ($inner !== '') {
                return $inner;
            }
        }

        $waybills = $response['waybills'] ?? $response['waybill'] ?? null;
        if (is_array($waybills)) {
            // Lista OrderWaybill
            if (isset($waybills[0]) && is_array($waybills[0])) {
                foreach (['number', 'waybillno', 'waybill_number', 'tracking_number'] as $key) {
                    $value = trim((string) ($waybills[0][$key] ?? ''));
                    if ($value !== '') {
                        return $value;
                    }
                }
            }
            // Pojedynczy obiekt
            foreach (['number', 'waybillno', 'waybill_number', 'tracking_number'] as $key) {
                $value = trim((string) ($waybills[$key] ?? ''));
                if ($value !== '') {
                    return $value;
                }
            }
        }

        foreach (['waybillno', 'waybill_number', 'parcel_number', 'tracking_number'] as $key) {
            $value = trim((string) ($response[$key] ?? ''));
            if ($value !== '') {
                return $value;
            }
        }

        return $orderno;
    }

    private function resolveStorageRoot(): string
    {
        $root = dirname(__DIR__, 3) . '/storage';
        return is_dir($root) ? $root : sys_get_temp_dir();
    }

    /**
     * @return array{login: string, api_token: string, default_label_format: string, integration_id: int}
     */
    private function requireCredentials(): array
    {
        $credentials = $this->integrationRepository->getCredentials();
        if ($credentials === null) {
            throw new IntegrationConfigException('Brak konfiguracji polkurier (login/Token API/aktywnosc).');
        }
        return $credentials;
    }

    /**
     * @param array<string, mixed> $sender
     */
    private function validateSender(array $sender): void
    {
        $required = ['street', 'city', 'postalCode'];
        foreach ($required as $key) {
            if (trim((string) ($sender[$key] ?? '')) === '') {
                throw new ShipmentException('Niekompletne dane nadawcy w Ustawieniach firmy (ulica/miasto/kod pocztowy).');
            }
        }
        $name = trim((string) ($sender['name'] ?? ''));
        $company = trim((string) ($sender['company'] ?? ''));
        if ($name === '' && $company === '') {
            throw new ShipmentException('Niekompletne dane nadawcy w Ustawieniach firmy (imie/nazwisko lub nazwa firmy).');
        }
    }

    /**
     * @param array<string, mixed> $sender
     * @return array<string, mixed>
     */
    private function buildSenderPayload(array $sender): array
    {
        $street = trim((string) ($sender['street'] ?? ''));
        $parsed = $this->splitStreetAndNumber($street);

        return [
            'company' => trim((string) ($sender['company'] ?? '')),
            'person' => trim((string) ($sender['contactPerson'] ?? $sender['name'] ?? '')),
            'street' => $parsed['street'],
            'housenumber' => $parsed['house'],
            'flatnumber' => $parsed['flat'],
            'postcode' => trim((string) ($sender['postalCode'] ?? '')),
            'city' => trim((string) ($sender['city'] ?? '')),
            'email' => trim((string) ($sender['email'] ?? '')),
            'phone' => $this->normalizePhone((string) ($sender['phone'] ?? '')),
            'country' => strtoupper(trim((string) ($sender['countryCode'] ?? 'PL'))) ?: 'PL',
        ];
    }

    /**
     * @param array<string, mixed> $order
     * @param array<string, mixed> $formData
     * @return array<string, mixed>
     */
    private function buildRecipient(array $order, array $formData, string $receiverPointId): array
    {
        $addresses = is_array($order['addresses'] ?? null) ? $order['addresses'] : [];
        $delivery = [];
        $customer = [];
        foreach ($addresses as $addr) {
            $type = (string) ($addr['address_type'] ?? '');
            if ($type === 'delivery') {
                $delivery = is_array($addr) ? $addr : [];
            } elseif ($type === 'customer') {
                $customer = is_array($addr) ? $addr : [];
            }
        }

        $name = $this->firstNonEmpty([
            $formData['receiver_name'] ?? null,
            $delivery['name'] ?? null,
            $customer['name'] ?? null,
            $delivery['company_name'] ?? null,
            $customer['company_name'] ?? null,
            'Klient',
        ]);
        $company = $this->firstNonEmpty([
            $formData['receiver_company'] ?? null,
            $delivery['company_name'] ?? null,
            $customer['company_name'] ?? null,
        ]);
        $streetLine = $this->firstNonEmpty([
            $formData['receiver_street'] ?? null,
            $this->composeStreet($delivery),
            $this->composeStreet($customer),
        ]);
        $parsed = $this->splitStreetAndNumber($streetLine);
        $postcode = $this->firstNonEmpty([
            $formData['receiver_postal_code'] ?? null,
            $delivery['zip_code'] ?? null,
            $customer['zip_code'] ?? null,
        ]);
        $city = $this->firstNonEmpty([
            $formData['receiver_city'] ?? null,
            $delivery['city'] ?? null,
            $customer['city'] ?? null,
        ]);
        $country = strtoupper($this->firstNonEmpty([
            $formData['receiver_country_code'] ?? null,
            $delivery['country'] ?? null,
            $customer['country'] ?? null,
            'PL',
        ]));
        $phone = $this->firstNonEmpty([
            $formData['receiver_phone'] ?? null,
            $delivery['phone'] ?? null,
            $customer['phone'] ?? null,
        ]);
        $email = $this->firstNonEmpty([
            $formData['receiver_email'] ?? null,
            $delivery['email'] ?? null,
            $customer['email'] ?? null,
        ]);

        if ($name === '' || $postcode === '' || $city === '') {
            throw new ShipmentException('Brak wymaganych danych adresowych odbiorcy (imie/kod pocztowy/miasto).');
        }
        if ($receiverPointId === '' && $parsed['street'] === '') {
            throw new ShipmentException('Brak ulicy odbiorcy (wymagana dla uslug kurierskich).');
        }

        return [
            'company' => $company,
            'person' => $name,
            'street' => $parsed['street'],
            'housenumber' => $parsed['house'],
            'flatnumber' => $parsed['flat'],
            'postcode' => $postcode,
            'city' => $city,
            'email' => $email,
            'phone' => $this->normalizePhone($phone),
            'country' => $country !== '' ? $country : 'PL',
            'point_id' => $receiverPointId,
        ];
    }

    /**
     * @param array<string, mixed> $formData
     * @return array<string, mixed>
     */
    private function buildPickup(array $formData): array
    {
        $date = trim((string) ($formData['pickup_date'] ?? ''));
        if ($date === '' || preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) !== 1) {
            $date = $this->nextBusinessDay();
        }
        $from = trim((string) ($formData['pickup_time_from'] ?? '10:00'));
        $to = trim((string) ($formData['pickup_time_to'] ?? '16:00'));
        $noCourierOrder = !empty($formData['no_courier_order']);

        return [
            'pickupdate' => $date,
            'pickuptimefrom' => $from,
            'pickuptimeto' => $to,
            'nocourierorder' => $noCourierOrder,
        ];
    }

    private function nextBusinessDay(): string
    {
        $ts = time();
        do {
            $ts = strtotime('+1 day', $ts);
            $dow = (int) date('N', $ts ?: time());
        } while ($dow >= 6);
        return date('Y-m-d', $ts ?: time());
    }

    /**
     * @return array{street: string, house: string, flat: string}
     */
    private function splitStreetAndNumber(string $streetLine): array
    {
        $street = trim($streetLine);
        if ($street === '') {
            return ['street' => '', 'house' => '', 'flat' => ''];
        }

        // Wzorce: "Marszalkowska 10/5", "Marszalkowska 10 m. 5", "Marszalkowska 10A"
        if (preg_match('/^(.*?)\s+(\d+[A-Za-z]?)(?:\s*[\/\-]\s*|\s*m\.?\s*)?(\d+[A-Za-z]?)?$/u', $street, $matches) === 1) {
            return [
                'street' => trim((string) $matches[1]),
                'house' => trim((string) ($matches[2] ?? '')),
                'flat' => trim((string) ($matches[3] ?? '')),
            ];
        }

        return ['street' => $street, 'house' => '', 'flat' => ''];
    }

    /**
     * @param array<string, mixed> $address
     */
    private function composeStreet(array $address): string
    {
        $street = trim((string) ($address['street_name'] ?? ''));
        $number = trim((string) ($address['street_number'] ?? ''));
        if ($street === '') {
            return '';
        }
        return $number !== '' ? trim($street . ' ' . $number) : $street;
    }

    /**
     * @param array<int, mixed> $candidates
     */
    private function firstNonEmpty(array $candidates): string
    {
        foreach ($candidates as $candidate) {
            $value = trim((string) $candidate);
            if ($value !== '') {
                return $value;
            }
        }
        return '';
    }

    private function normalizePhone(string $phone): string
    {
        $digits = preg_replace('/\D+/', '', $phone) ?? '';
        if ($digits === '') {
            return '';
        }
        // Polkurier akceptuje cyfry. Usun prefiks 48 jezeli jest podwojny.
        if (str_starts_with($digits, '48') && strlen($digits) === 11) {
            $digits = substr($digits, 2);
        }
        return $digits;
    }

    /**
     * @param array<string, mixed> $carrier
     */
    private function detectPickupPointSupport(string $code, array $carrier): bool
    {
        $haystack = strtolower($code . ' ' . trim((string) ($carrier['name'] ?? '')));
        return str_contains($haystack, 'paczkomat')
            || str_contains($haystack, 'parcel')
            || str_contains($haystack, 'inpost')
            || str_contains($haystack, 'orlen')
            || str_contains($haystack, 'pocztex')
            || str_contains($haystack, 'kurier48')
            || str_contains($haystack, 'punkt');
    }

    private function resolvePointCourierKey(string $code): ?string
    {
        $lower = strtolower($code);
        if (str_contains($lower, 'inpost') || str_contains($lower, 'paczkomat')) {
            return 'inpost';
        }
        if (str_contains($lower, 'orlen')) {
            return 'orlen';
        }
        if (str_contains($lower, 'pocztex')) {
            return 'pocztex';
        }
        if (str_contains($lower, 'kurier48')) {
            return 'kurier48';
        }
        return null;
    }

    /**
     * polkurier API wymaga lowercase z dozwolonego zbioru:
     * [box, envelope, palette, small_parcel, parcel_size_20].
     * Mapuje istniejace orderPRO wartosci (PACKAGE/BOX/ENVELOPE/...) na format polkurier.
     */
    private function normalizeShipmentType(string $input): string
    {
        $raw = strtolower(trim($input));
        $allowed = ['box', 'envelope', 'palette', 'small_parcel', 'parcel_size_20'];
        if (in_array($raw, $allowed, true)) {
            return $raw;
        }

        $aliases = [
            'package' => 'box',
            'parcel' => 'box',
            'paczka' => 'box',
            'koperta' => 'envelope',
            'paleta' => 'palette',
            'mala_paczka' => 'small_parcel',
            'small' => 'small_parcel',
        ];
        return $aliases[$raw] ?? 'box';
    }

    private function resolveCarrierLabel(string $courierCode): string
    {
        foreach ($this->getDeliveryServices() as $service) {
            if (strcasecmp((string) ($service['id'] ?? ''), $courierCode) === 0) {
                return (string) ($service['name'] ?? $courierCode);
            }
        }
        return $courierCode;
    }
}
