<?php

declare(strict_types=1);

namespace Gls\GlsPoland\PrestaShop\Order;

use Gls\GlsPoland\Configuration\Orders;
use Gls\GlsPoland\Configuration\SenderAddressConfigurationInterface;
use Gls\GlsPoland\Consignment\DTO\AddressDetails;
use Gls\GlsPoland\Consignment\DTO\Parcel;
use Gls\GlsPoland\Consignment\DTO\ServiceDetails;
use Gls\GlsPoland\Message\CreateConsignmentCommand;
use Gls\GlsPoland\PrestaShop\Order\Exception\OrderNotFoundException;
use Gls\GlsPoland\PrestaShop\ObjectModel\Repository\OrderRepository;
use Gls\GlsPoland\Repository\OrderParcelShopRepository;

final class CreateConsignmentCommandFactory implements CreateConsignmentCommandFactoryInterface
{
    private $orderRepository;
    private $addressDetailsFactory;
    private $serviceDetailsFactory;
    private $ordersConfiguration;
    private $senderAddressConfiguration;
    private $orderParcelShopRepository;

    public function __construct(
        OrderRepository $orderRepository,
        AddressDetailsFactoryInterface $addressDetailsFactory,
        ServiceDetailsFactory $serviceDetailsFactory,
        Orders\ConfigurationInterface $ordersConfiguration,
        SenderAddressConfigurationInterface $senderAddressConfiguration,
        OrderParcelShopRepository $orderParcelShopRepository
    ) {
        $this->orderRepository = $orderRepository;
        $this->addressDetailsFactory = $addressDetailsFactory;
        $this->serviceDetailsFactory = $serviceDetailsFactory;
        $this->ordersConfiguration = $ordersConfiguration;
        $this->senderAddressConfiguration = $senderAddressConfiguration;
        $this->orderParcelShopRepository = $orderParcelShopRepository;
    }

    public function create(int $orderId): CreateConsignmentCommand
    {
        if (null === $order = $this->orderRepository->find($orderId)) {
            throw OrderNotFoundException::create($orderId);
        }

        $recipientDetails = $this->addressDetailsFactory->create((int) $order->id_address_delivery);
        $serviceDetails = $this->serviceDetailsFactory->create($order);
        if ($serviceDetails->isShopDelivery()) {
            $this->applyParcelShopRecipientZipCode((int) $order->id, $recipientDetails);
        }

        $senderDetails = $this->senderAddressConfiguration->getSenderAddressDetails();
        $parcel = $this->createParcel($order);

        $command = new CreateConsignmentCommand(
            (int) $order->id,
            $recipientDetails,
            $senderDetails,
            $serviceDetails,
            $parcel
        );

        return $command
            ->setReferences($this->ordersConfiguration->getDefaultReferences())
            ->setNotes($this->ordersConfiguration->getDefaultNotes());
    }

    private function applyParcelShopRecipientZipCode(int $orderId, AddressDetails $recipientDetails): void
    {
        $parcelShop = $this->orderParcelShopRepository->find($orderId);
        if (null === $parcelShop) {
            return;
        }

        $postalCode = $this->normalizePolishZipCode((string) $parcelShop->getPostalCode());
        if ('' === $postalCode) {
            return;
        }

        // For SDS, ADE validates receiver zipcode against pickup point.
        $recipientDetails->setZipCode($postalCode);
    }

    private function normalizePolishZipCode(string $zipCode): string
    {
        $zipCode = trim($zipCode);
        if ('' === $zipCode) {
            return '';
        }

        $zipCode = preg_replace('/\s+/', '', $zipCode) ?? $zipCode;
        $digits = preg_replace('/\D+/', '', $zipCode) ?? '';

        if (5 === strlen($digits)) {
            return sprintf('%s-%s', substr($digits, 0, 2), substr($digits, 2, 3));
        }

        return strtoupper($zipCode);
    }

    private function createParcel(\Order $order): Parcel
    {
        $weight = $order->getTotalWeight() ?: $this->ordersConfiguration->getDefaultParcelWeightKg();

        return (new Parcel())
            ->setWeightKg($weight)
            ->setReference($order->reference);
    }
}