<?php
declare(strict_types=1);

namespace App\Modules\Settings;

use App\Core\Support\StringHelper;
use App\Modules\Orders\OrderImportRepository;
use App\Modules\Orders\OrdersRepository;
use App\Core\Constants\IntegrationSources;
use App\Core\Exceptions\AllegroApiException;
use RuntimeException;
use Throwable;

final class AllegroOrderImportService
{
    private const IMPORT_TRIGGERS = [
        'manual_import' => 'Import reczny',
        'orders_sync' => 'Synchronizacja zamowien',
        'status_sync' => 'Synchronizacja statusow',
    ];

    public function __construct(
        private readonly AllegroIntegrationRepository $integrationRepository,
        private readonly AllegroTokenManager $tokenManager,
        private readonly AllegroApiClient $apiClient,
        private readonly OrderImportRepository $orders,
        private readonly AllegroStatusMappingRepository $statusMappings,
        private readonly OrdersRepository $ordersRepository
    ) {
    }

    /**
     * @return array<string, mixed>
     */
    public function importSingleOrder(string $checkoutFormId, string $trigger = 'manual_import'): array
    {
        $orderId = trim($checkoutFormId);
        if ($orderId === '') {
            throw new AllegroApiException('Podaj ID zamowienia Allegro.');
        }
        $normalizedTrigger = $this->normalizeTrigger($trigger);
        $triggerLabel = self::IMPORT_TRIGGERS[$normalizedTrigger];

        [$accessToken, $env] = $this->tokenManager->resolveToken();

        try {
            $payload = $this->apiClient->getCheckoutForm($env, $accessToken, $orderId);
        } catch (RuntimeException $exception) {
            if (trim($exception->getMessage()) !== 'ALLEGRO_HTTP_401') {
                throw $exception;
            }

            [$accessToken, $env] = $this->tokenManager->resolveToken();
            $payload = $this->apiClient->getCheckoutForm($env, $accessToken, $orderId);
        }

        $mapped = $this->mapCheckoutFormPayload($payload, $env, $accessToken);
        $saveResult = $this->orders->upsertOrderAggregate(
            $mapped['order'],
            $mapped['addresses'],
            $mapped['items'],
            $mapped['payments'],
            $mapped['shipments'],
            $mapped['notes'],
            $mapped['status_history']
        );

        $savedOrderId = (int) ($saveResult['order_id'] ?? 0);
        $wasCreated = !empty($saveResult['created']);

        if ($savedOrderId > 0) {
            $sourceUpdatedAt = trim((string) ($mapped['order']['source_updated_at'] ?? ''));
            $summary = $wasCreated
                ? 'Zaimportowano zamowienie z Allegro'
                : 'Zaktualizowano zamowienie z Allegro (re-import)';
            $details = [
                'source' => IntegrationSources::ALLEGRO,
                'source_order_id' => trim($checkoutFormId),
                'source_updated_at' => $sourceUpdatedAt,
                'created' => $wasCreated,
                'trigger' => $normalizedTrigger,
                'trigger_label' => $triggerLabel,
            ];

            if (!$this->ordersRepository->shouldSkipDuplicateImportActivity($savedOrderId, $details)) {
                $this->ordersRepository->recordActivity(
                    $savedOrderId,
                    'import',
                    $summary,
                    $details,
                    'import',
                    'Allegro'
                );
            }
        }

        return [
            'order_id' => $savedOrderId,
            'created' => $wasCreated,
            'source_order_id' => (string) ($mapped['order']['source_order_id'] ?? ''),
            'image_diagnostics' => (array) ($mapped['image_diagnostics'] ?? []),
        ];
    }

    private function normalizeTrigger(string $trigger): string
    {
        $value = trim($trigger);
        if ($value === '' || !array_key_exists($value, self::IMPORT_TRIGGERS)) {
            return 'manual_import';
        }

        return $value;
    }

    /**
     * @param array<string, mixed> $payload
     * @return array{
     *   order:array<string, mixed>,
     *   addresses:array<int, array<string, mixed>>,
     *   items:array<int, array<string, mixed>>,
     *   image_diagnostics:array<string, mixed>,
     *   payments:array<int, array<string, mixed>>,
     *   shipments:array<int, array<string, mixed>>,
     *   notes:array<int, array<string, mixed>>,
     *   status_history:array<int, array<string, mixed>>
     * }
     */
    private function mapCheckoutFormPayload(array $payload, string $environment, string $accessToken): array
    {
        $checkoutFormId = trim((string) ($payload['id'] ?? ''));
        if ($checkoutFormId === '') {
            throw new AllegroApiException('Odpowiedz Allegro nie zawiera ID zamowienia.');
        }

        $status = trim((string) ($payload['status'] ?? ''));
        $fulfillmentStatus = trim((string) ($payload['fulfillment']['status'] ?? ''));
        $rawAllegroStatus = strtolower($fulfillmentStatus !== '' ? $fulfillmentStatus : $status);
        $mappedOrderproStatus = $this->statusMappings->findMappedOrderproStatusCode($rawAllegroStatus);
        $externalStatus = $mappedOrderproStatus !== null ? $mappedOrderproStatus : $rawAllegroStatus;
        $paymentStatusRaw = strtolower(trim((string) ($payload['payment']['status'] ?? '')));

        $totalWithTax = $this->amountToFloat($payload['summary']['totalToPay'] ?? null);
        $totalPaid = $this->amountToFloat($payload['summary']['paidAmount'] ?? null);
        if ($totalPaid === null) {
            $totalPaid = $this->amountToFloat($payload['payment']['paidAmount'] ?? null);
        }
        if ($totalPaid === null) {
            $totalPaid = $this->amountToFloat($payload['payment']['amount'] ?? null);
        }

        $currency = trim((string) ($payload['summary']['totalToPay']['currency'] ?? ''));
        if ($currency === '') {
            $currency = trim((string) ($payload['payment']['amount']['currency'] ?? 'PLN'));
        }
        if ($currency === '') {
            $currency = 'PLN';
        }

        $buyer = is_array($payload['buyer'] ?? null) ? $payload['buyer'] : [];
        $delivery = is_array($payload['delivery'] ?? null) ? $payload['delivery'] : [];
        $invoice = is_array($payload['invoice'] ?? null) ? $payload['invoice'] : [];
        $payment = is_array($payload['payment'] ?? null) ? $payload['payment'] : [];
        $lineItems = is_array($payload['lineItems'] ?? null) ? $payload['lineItems'] : [];
        $deliveryMethod = is_array($delivery['method'] ?? null) ? $delivery['method'] : [];
        $deliveryMethodId = trim((string) ($deliveryMethod['id'] ?? ''));
        $deliveryMethodName = trim((string) ($deliveryMethod['name'] ?? ''));
        $deliveryForm = $deliveryMethodName !== '' ? $deliveryMethodName : $deliveryMethodId;
        $deliveryTime = is_array($delivery['time'] ?? null) ? $delivery['time'] : [];
        $dispatchTime = is_array($deliveryTime['dispatch'] ?? null) ? $deliveryTime['dispatch'] : [];
        $sendDateMin = StringHelper::normalizeDateTime((string) ($dispatchTime['from'] ?? ''));
        $sendDateMax = StringHelper::normalizeDateTime((string) ($dispatchTime['to'] ?? ''));
        if ($sendDateMin === null) {
            $sendDateMin = StringHelper::normalizeDateTime((string) ($deliveryTime['from'] ?? ''));
        }
        if ($sendDateMax === null) {
            $sendDateMax = StringHelper::normalizeDateTime((string) ($deliveryTime['to'] ?? ''));
        }

        $boughtAt = StringHelper::normalizeDateTime((string) ($payload['boughtAt'] ?? ''));
        $updatedAt = StringHelper::normalizeDateTime((string) ($payload['updatedAt'] ?? ''));
        $fetchedAt = date('Y-m-d H:i:s');

        $order = [
            'integration_id' => $this->integrationRepository->getActiveIntegrationId(),
            'source' => IntegrationSources::ALLEGRO,
            'source_order_id' => $checkoutFormId,
            'external_order_id' => $checkoutFormId,
            'external_platform_id' => trim((string) ($payload['marketplace']['id'] ?? 'allegro-pl')),
            'external_platform_account_id' => null,
            'external_status_id' => $externalStatus,
            'external_payment_type_id' => trim((string) ($payment['type'] ?? '')),
            'payment_status' => $this->mapPaymentStatus($paymentStatusRaw),
            'external_carrier_id' => $deliveryForm !== '' ? $deliveryForm : null,
            'external_carrier_account_id' => $deliveryMethodId !== '' ? $deliveryMethodId : null,
            'customer_login' => trim((string) ($buyer['login'] ?? '')),
            'is_invoice' => !empty($invoice['required']),
            'is_encrypted' => false,
            'is_canceled_by_buyer' => in_array($externalStatus, ['cancelled', 'canceled'], true),
            'currency' => strtoupper($currency),
            'total_without_tax' => null,
            'total_with_tax' => $totalWithTax,
            'total_paid' => $totalPaid,
            'send_date_min' => $sendDateMin,
            'send_date_max' => $sendDateMax,
            'ordered_at' => $boughtAt,
            'source_created_at' => $boughtAt,
            'source_updated_at' => $updatedAt,
            'preferences_json' => [
                'status' => $status,
                'fulfillment_status' => $fulfillmentStatus,
                'allegro_status_raw' => $rawAllegroStatus,
                'payment_status' => $paymentStatusRaw,
                'delivery_method_name' => $deliveryMethodName,
                'delivery_method_id' => $deliveryMethodId,
                'delivery_cost' => $delivery['cost'] ?? null,
                'delivery_time' => $deliveryTime,
            ],
            'payload_json' => $payload,
            'fetched_at' => $fetchedAt,
        ];

        $addresses = $this->buildAddresses($buyer, $delivery, $invoice);
        $itemsResult = $this->buildItems($lineItems, $environment, $accessToken);
        $items = (array) ($itemsResult['items'] ?? []);
        $payments = $this->buildPayments($payment, $currency);
        $apiShipments = [];
        try {
            $apiShipments = $this->apiClient->getCheckoutFormShipments($environment, $accessToken, $checkoutFormId);
        } catch (Throwable) {
        }
        $shipments = $this->buildShipments($apiShipments, $delivery);
        $notes = $this->buildNotes($payload);
        $statusHistory = [[
            'from_status_id' => null,
            'to_status_id' => $externalStatus !== '' ? $externalStatus : 'unknown',
            'changed_at' => $updatedAt !== null ? $updatedAt : $fetchedAt,
            'change_source' => 'import',
            'comment' => 'Import z Allegro checkout form',
            'payload_json' => [
                'status' => $status,
                'fulfillment_status' => $fulfillmentStatus,
                'allegro_status_raw' => $rawAllegroStatus,
            ],
        ]];

        return [
            'order' => $order,
            'addresses' => $addresses,
            'items' => $items,
            'image_diagnostics' => (array) ($itemsResult['image_diagnostics'] ?? []),
            'payments' => $payments,
            'shipments' => $shipments,
            'notes' => $notes,
            'status_history' => $statusHistory,
        ];
    }

    /**
     * @param array<string, mixed> $buyer
     * @param array<string, mixed> $delivery
     * @param array<string, mixed> $invoice
     * @return array<int, array<string, mixed>>
     */
    private function buildAddresses(array $buyer, array $delivery, array $invoice): array
    {
        $result = [];

        $customerName = trim((string) (($buyer['firstName'] ?? '') . ' ' . ($buyer['lastName'] ?? '')));
        if ($customerName === '') {
            $customerName = trim((string) ($buyer['login'] ?? ''));
        }
        if ($customerName === '') {
            $customerName = 'Kupujacy Allegro';
        }

        $result[] = [
            'address_type' => 'customer',
            'name' => $customerName,
            'phone' => StringHelper::nullableString((string) ($buyer['phoneNumber'] ?? '')),
            'email' => StringHelper::nullableString((string) ($buyer['email'] ?? '')),
            'street_name' => null,
            'street_number' => null,
            'city' => null,
            'zip_code' => null,
            'country' => null,
            'department' => null,
            'parcel_external_id' => null,
            'parcel_name' => null,
            'address_class' => null,
            'company_tax_number' => null,
            'company_name' => null,
            'payload_json' => $buyer,
        ];

        $deliveryAddress = is_array($delivery['address'] ?? null) ? $delivery['address'] : [];
        $pickupPoint = is_array($delivery['pickupPoint'] ?? null) ? $delivery['pickupPoint'] : [];
        $pickupAddress = is_array($pickupPoint['address'] ?? null) ? $pickupPoint['address'] : [];
        if ($deliveryAddress !== [] || $pickupAddress !== []) {
            $isPickupPointDelivery = $pickupAddress !== [];

            // Always use recipient's personal data from delivery.address for name/phone/email.
            // For pickup points, delivery.address still holds the recipient's data (not the machine location).
            $name = $this->fallbackName($deliveryAddress, 'Dostawa');

            $street = $isPickupPointDelivery
                ? StringHelper::nullableString((string) ($pickupAddress['street'] ?? ''))
                : StringHelper::nullableString((string) ($deliveryAddress['street'] ?? ''));
            $city = $isPickupPointDelivery
                ? StringHelper::nullableString((string) ($pickupAddress['city'] ?? ''))
                : StringHelper::nullableString((string) ($deliveryAddress['city'] ?? ''));
            $zipCode = $isPickupPointDelivery
                ? StringHelper::nullableString((string) ($pickupAddress['zipCode'] ?? ''))
                : StringHelper::nullableString((string) ($deliveryAddress['zipCode'] ?? ''));
            $country = $isPickupPointDelivery
                ? StringHelper::nullableString((string) ($pickupAddress['countryCode'] ?? ''))
                : StringHelper::nullableString((string) ($deliveryAddress['countryCode'] ?? ''));

            $deliveryPhone = trim((string) ($deliveryAddress['phoneNumber'] ?? ''));
            $buyerPhone = trim((string) ($buyer['phoneNumber'] ?? ''));

            $result[] = [
                'address_type' => 'delivery',
                'name' => $name,
                'phone' => StringHelper::nullableString($deliveryPhone !== '' ? $deliveryPhone : $buyerPhone),
                'email' => StringHelper::nullableString((string) ($deliveryAddress['email'] ?? $buyer['email'] ?? '')),
                'street_name' => $street,
                'street_number' => null,
                'city' => $city,
                'zip_code' => $zipCode,
                'country' => $country,
                'department' => null,
                'parcel_external_id' => StringHelper::nullableString((string) ($pickupPoint['id'] ?? '')),
                'parcel_name' => StringHelper::nullableString((string) ($pickupPoint['name'] ?? '')),
                'address_class' => null,
                'company_tax_number' => null,
                'company_name' => StringHelper::nullableString((string) ($deliveryAddress['companyName'] ?? '')),
                'payload_json' => [
                    'address' => $deliveryAddress,
                    'pickup_point' => $pickupPoint,
                ],
            ];
        }

        $invoiceAddress = is_array($invoice['address'] ?? null) ? $invoice['address'] : [];
        if ($invoiceAddress !== []) {
            $result[] = [
                'address_type' => 'invoice',
                'name' => $this->fallbackName($invoiceAddress, 'Faktura'),
                'phone' => StringHelper::nullableString((string) ($invoiceAddress['phoneNumber'] ?? '')),
                'email' => StringHelper::nullableString((string) ($invoiceAddress['email'] ?? '')),
                'street_name' => StringHelper::nullableString((string) ($invoiceAddress['street'] ?? '')),
                'street_number' => null,
                'city' => StringHelper::nullableString((string) ($invoiceAddress['city'] ?? '')),
                'zip_code' => StringHelper::nullableString((string) ($invoiceAddress['zipCode'] ?? '')),
                'country' => StringHelper::nullableString((string) ($invoiceAddress['countryCode'] ?? '')),
                'department' => null,
                'parcel_external_id' => null,
                'parcel_name' => null,
                'address_class' => null,
                'company_tax_number' => StringHelper::nullableString((string) ($invoiceAddress['taxId'] ?? '')),
                'company_name' => StringHelper::nullableString((string) ($invoiceAddress['companyName'] ?? '')),
                'payload_json' => $invoiceAddress,
            ];
        }

        return $result;
    }

    /**
     * @param array<int, mixed> $lineItems
     * @return array{
     *   items:array<int, array<string, mixed>>,
     *   image_diagnostics:array<string, mixed>
     * }
     */
    private function buildItems(array $lineItems, string $environment, string $accessToken): array
    {
        $result = [];
        $offerImageCache = [];
        $diagnostics = [
            'total_items' => 0,
            'with_image' => 0,
            'without_image' => 0,
            'source_counts' => [
                'checkout_form' => 0,
                'offer_api' => 0,
            ],
            'reason_counts' => [],
            'sample_issues' => [],
        ];
        $sortOrder = 0;
        foreach ($lineItems as $itemRaw) {
            if (!is_array($itemRaw)) {
                continue;
            }

            $diagnostics['total_items'] = (int) $diagnostics['total_items'] + 1;
            $offer = is_array($itemRaw['offer'] ?? null) ? $itemRaw['offer'] : [];
            $name = trim((string) ($offer['name'] ?? ''));
            if ($name === '') {
                $name = 'Pozycja Allegro';
            }

            $offerId = trim((string) ($offer['id'] ?? ''));
            $mediaUrl = $this->extractLineItemImageUrl($itemRaw);
            $imageSource = 'none';
            $missingReason = null;
            if ($mediaUrl === null && $offerId !== '') {
                $offerImageResult = $this->resolveOfferImageUrlFromApi($offerId, $environment, $accessToken, $offerImageCache);
                $mediaUrl = $offerImageResult['url'];
                if ($mediaUrl !== null) {
                    $imageSource = 'offer_api';
                } else {
                    $missingReason = $offerImageResult['reason'];
                }
            } elseif ($mediaUrl === null) {
                $missingReason = 'missing_offer_id';
            } else {
                $imageSource = 'checkout_form';
            }

            if ($mediaUrl !== null) {
                $diagnostics['with_image'] = (int) $diagnostics['with_image'] + 1;
                if ($imageSource === 'offer_api') {
                    $diagnostics['source_counts']['offer_api'] = (int) ($diagnostics['source_counts']['offer_api'] ?? 0) + 1;
                } else {
                    $diagnostics['source_counts']['checkout_form'] = (int) ($diagnostics['source_counts']['checkout_form'] ?? 0) + 1;
                }
            } else {
                $diagnostics['without_image'] = (int) $diagnostics['without_image'] + 1;
                $reasonCode = $missingReason ?? 'missing_in_checkout_form';
                $reasonCounts = is_array($diagnostics['reason_counts']) ? $diagnostics['reason_counts'] : [];
                $reasonCounts[$reasonCode] = (int) ($reasonCounts[$reasonCode] ?? 0) + 1;
                $diagnostics['reason_counts'] = $reasonCounts;

                $sampleIssues = is_array($diagnostics['sample_issues']) ? $diagnostics['sample_issues'] : [];
                if (count($sampleIssues) < 5) {
                    $sampleIssues[] = [
                        'offer_id' => $offerId,
                        'name' => $name,
                        'reason' => $reasonCode,
                    ];
                }
                $diagnostics['sample_issues'] = $sampleIssues;
            }

            $result[] = [
                'source_item_id' => StringHelper::nullableString((string) ($itemRaw['id'] ?? '')),
                'external_item_id' => StringHelper::nullableString((string) ($offer['id'] ?? '')),
                'ean' => null,
                'sku' => null,
                'original_name' => $name,
                'original_code' => StringHelper::nullableString((string) ($offer['id'] ?? '')),
                'original_price_with_tax' => $this->amountToFloat($itemRaw['originalPrice'] ?? null),
                'original_price_without_tax' => null,
                'media_url' => $mediaUrl,
                'quantity' => (float) ($itemRaw['quantity'] ?? 1),
                'tax_rate' => null,
                'item_status' => null,
                'unit' => 'pcs',
                'item_type' => 'product',
                'source_product_id' => StringHelper::nullableString((string) ($offer['id'] ?? '')),
                'source_product_set_id' => null,
                'sort_order' => $sortOrder++,
                'payload_json' => $itemRaw,
            ];
        }

        return [
            'items' => $result,
            'image_diagnostics' => $diagnostics,
        ];
    }

    /**
     * @param array<string, array{url:?string, reason:?string}> $offerImageCache
     * @return array{url:?string, reason:?string}
     */
    private function resolveOfferImageUrlFromApi(
        string $offerId,
        string $environment,
        string $accessToken,
        array &$offerImageCache
    ): array {
        if (array_key_exists($offerId, $offerImageCache)) {
            return $offerImageCache[$offerId];
        }

        try {
            $offerPayload = $this->apiClient->getProductOffer($environment, $accessToken, $offerId);
            $url = $this->extractOfferImageUrl($offerPayload);
            if ($url !== null) {
                $offerImageCache[$offerId] = ['url' => $url, 'reason' => null];
                return $offerImageCache[$offerId];
            }
            $offerImageCache[$offerId] = ['url' => null, 'reason' => 'missing_in_offer_api'];
        } catch (Throwable $exception) {
            $reason = $this->mapOfferApiErrorToReason($exception->getMessage());
            $offerImageCache[$offerId] = ['url' => null, 'reason' => $reason];
        }

        return $offerImageCache[$offerId];
    }

    private function mapOfferApiErrorToReason(string $message): string
    {
        $normalized = strtoupper(trim($message));
        if (str_contains($normalized, 'HTTP 403')) {
            return 'offer_api_access_denied_403';
        }
        if (str_contains($normalized, 'HTTP 401')) {
            return 'offer_api_unauthorized_401';
        }
        if (str_contains($normalized, 'HTTP 404')) {
            return 'offer_api_not_found_404';
        }
        if (preg_match('/HTTP\s+(\d{3})/', $normalized, $matches) === 1) {
            return 'offer_api_http_' . $matches[1];
        }

        return 'offer_api_request_failed';
    }

    /**
     * @param array<string, mixed> $offerPayload
     */
    private function extractOfferImageUrl(array $offerPayload): ?string
    {
        $candidates = [
            (string) ($offerPayload['imageUrl'] ?? ''),
            (string) ($offerPayload['image']['url'] ?? ''),
        ];

        $images = $offerPayload['images'] ?? null;
        if (is_array($images)) {
            $firstImage = $images[0] ?? null;
            if (is_array($firstImage)) {
                $candidates[] = (string) ($firstImage['url'] ?? '');
            } elseif (is_string($firstImage)) {
                $candidates[] = $firstImage;
            }
        }

        $productSet = $offerPayload['productSet'] ?? null;
        if (is_array($productSet)) {
            $firstSet = $productSet[0] ?? null;
            if (is_array($firstSet)) {
                $product = is_array($firstSet['product'] ?? null) ? $firstSet['product'] : [];
                $productImage = is_array($product['images'] ?? null) ? ($product['images'][0] ?? null) : null;
                if (is_array($productImage)) {
                    $candidates[] = (string) ($productImage['url'] ?? '');
                } elseif (is_string($productImage)) {
                    $candidates[] = $productImage;
                }
            }
        }

        foreach ($candidates as $candidate) {
            $url = trim($candidate);
            if ($url === '') {
                continue;
            }
            if (str_starts_with($url, '//')) {
                return 'https:' . $url;
            }
            if (!str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
                continue;
            }
            return $url;
        }

        return null;
    }

    /**
     * @param array<string, mixed> $itemRaw
     */
    private function extractLineItemImageUrl(array $itemRaw): ?string
    {
        $offer = is_array($itemRaw['offer'] ?? null) ? $itemRaw['offer'] : [];

        $candidates = [
            (string) ($itemRaw['imageUrl'] ?? ''),
            (string) ($offer['imageUrl'] ?? ''),
            (string) ($offer['image']['url'] ?? ''),
        ];

        $images = $offer['images'] ?? null;
        if (is_array($images)) {
            $firstImage = $images[0] ?? null;
            if (is_array($firstImage)) {
                $candidates[] = (string) ($firstImage['url'] ?? '');
            } elseif (is_string($firstImage)) {
                $candidates[] = $firstImage;
            }
        }

        foreach ($candidates as $candidate) {
            $url = trim($candidate);
            if ($url === '') {
                continue;
            }
            if (str_starts_with($url, '//')) {
                return 'https:' . $url;
            }
            if (!str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
                continue;
            }
            return $url;
        }

        return null;
    }

    /**
     * @param array<string, mixed> $payment
     * @return array<int, array<string, mixed>>
     */
    private function buildPayments(array $payment, string $fallbackCurrency): array
    {
        $paymentId = trim((string) ($payment['id'] ?? ''));
        if ($paymentId === '') {
            return [];
        }

        $amount = $this->amountToFloat($payment['paidAmount'] ?? null);
        if ($amount === null) {
            $amount = $this->amountToFloat($payment['amount'] ?? null);
        }

        return [[
            'source_payment_id' => $paymentId,
            'external_payment_id' => $paymentId,
            'payment_type_id' => trim((string) ($payment['type'] ?? 'allegro')),
            'payment_date' => StringHelper::normalizeDateTime((string) ($payment['finishedAt'] ?? '')),
            'amount' => $amount,
            'currency' => StringHelper::nullableString((string) ($payment['amount']['currency'] ?? $fallbackCurrency)),
            'comment' => StringHelper::nullableString((string) ($payment['provider'] ?? '')),
            'payload_json' => $payment,
        ]];
    }

    /**
     * @param array<int, array<string, mixed>> $apiShipments
     * @param array<string, mixed> $delivery
     * @return array<int, array<string, mixed>>
     */
    private function buildShipments(array $apiShipments, array $delivery): array
    {
        $result = [];
        foreach ($apiShipments as $shipmentRaw) {
            if (!is_array($shipmentRaw)) {
                continue;
            }
            $trackingNumber = trim((string) ($shipmentRaw['waybill'] ?? ''));
            if ($trackingNumber === '') {
                continue;
            }

            $carrierId = trim((string) ($shipmentRaw['carrierId'] ?? $delivery['method']['id'] ?? ''));
            $carrierName = trim((string) ($shipmentRaw['carrierName'] ?? ''));
            $result[] = [
                'source_shipment_id' => StringHelper::nullableString((string) ($shipmentRaw['id'] ?? '')),
                'external_shipment_id' => StringHelper::nullableString((string) ($shipmentRaw['id'] ?? '')),
                'tracking_number' => $trackingNumber,
                'carrier_provider_id' => $carrierId !== '' ? $carrierId : ($carrierName !== '' ? $carrierName : 'allegro'),
                'posted_at' => StringHelper::normalizeDateTime((string) ($shipmentRaw['createdAt'] ?? '')),
                'media_uuid' => null,
                'payload_json' => $shipmentRaw,
            ];
        }

        return $result;
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<int, array<string, mixed>>
     */
    private function buildNotes(array $payload): array
    {
        $message = trim((string) ($payload['messageToSeller'] ?? ''));
        if ($message === '') {
            return [];
        }

        return [[
            'source_note_id' => null,
            'note_type' => 'buyer_message',
            'created_at_external' => StringHelper::normalizeDateTime((string) ($payload['updatedAt'] ?? '')),
            'comment' => $message,
            'payload_json' => ['messageToSeller' => $message],
        ]];
    }

    private function mapPaymentStatus(string $status): ?int
    {
        return match ($status) {
            'paid', 'finished', 'completed' => 2,
            'partially_paid', 'in_progress' => 1,
            'cancelled', 'canceled', 'failed', 'unpaid' => 0,
            default => null,
        };
    }

    private function amountToFloat(mixed $amountNode): ?float
    {
        if (!is_array($amountNode)) {
            return null;
        }
        $value = trim((string) ($amountNode['amount'] ?? ''));
        if ($value === '' || !is_numeric($value)) {
            return null;
        }

        return (float) $value;
    }

    /**
     * @param array<string, mixed> $address
     */
    private function fallbackName(array $address, string $fallback): string
    {
        $name = trim((string) (($address['firstName'] ?? '') . ' ' . ($address['lastName'] ?? '')));
        if ($name !== '') {
            return $name;
        }

        $company = trim((string) ($address['companyName'] ?? ''));
        if ($company !== '') {
            return $company;
        }

        return $fallback;
    }

}
