<?php
/**
 * Copyright since 2025 InPost S.A.
 *
 * For the full license information, please view the LICENSE file bundled with the module.
 *
 * @author InPost S.A.
 * @copyright since 2025 InPost S.A.
 * @license MIT
 */

declare(strict_types=1);

namespace InPost\International\Shipment\MessageHandler;

use InPost\International\Entity\Shipment;
use InPost\International\PrestaShop\ObjectModel\Repository\ObjectRepositoryInterface;
use InPost\International\PrestaShop\ObjectModel\Repository\OrderRepository;
use InPost\International\Shipment\Exception\NoOrdersWithoutShipmentFoundException;
use InPost\International\Shipment\Exception\ShipmentException;
use InPost\International\Shipment\Message\BulkCreateShipmentsCommand;
use InPost\International\Shipment\Message\CreateShipmentCommand;
use InPost\International\Shipment\MessageHandler\Result\CreateShipmentsResult;
use InPost\International\Shipment\Order\CreateShipmentCommandBuilderInterface;
use InPost\International\Shipment\ShipmentRepositoryInterface;
use PrestaShop\PrestaShop\Core\CommandBus\CommandBusInterface;

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

final class BulkCreateShipmentsHandler
{
    /**
     * @var ShipmentRepositoryInterface
     */
    private $shipmentRepository;

    /**
     * @var OrderRepository
     */
    private $orderRepository;

    /**
     * @var CreateShipmentCommandBuilderInterface
     */
    private $commandBuilder;

    /**
     * @var CommandBusInterface
     */
    private $bus;

    /**
     * @param OrderRepository $orderRepository
     */
    public function __construct(ShipmentRepositoryInterface $shipmentRepository, ObjectRepositoryInterface $orderRepository, CreateShipmentCommandBuilderInterface $commandBuilder, CommandBusInterface $bus)
    {
        $this->shipmentRepository = $shipmentRepository;
        $this->orderRepository = $orderRepository;
        $this->commandBuilder = $commandBuilder;
        $this->bus = $bus;
    }

    public function __invoke(BulkCreateShipmentsCommand $command): CreateShipmentsResult
    {
        if ([] === $orders = $this->getOrdersWithoutShipment($command)) {
            throw new NoOrdersWithoutShipmentFoundException('No orders without shipment found.');
        }

        $shipments = [];
        $errors = [];

        foreach ($orders as $order) {
            $orderId = (int) $order->id;

            try {
                $shipments[$orderId] = $this->createShipment($order);
            } catch (\Exception $e) {
                $errors[$orderId] = $e;
            }
        }

        return new CreateShipmentsResult($shipments, $errors);
    }

    public function handle(BulkCreateShipmentsCommand $command): CreateShipmentsResult
    {
        return ($this)($command);
    }

    /**
     * @return \Order[]
     */
    private function getOrdersWithoutShipment(BulkCreateShipmentsCommand $command): array
    {
        if ([] === $orderIds = $command->getOrderIds()) {
            throw new ShipmentException('At least one order ID is required.');
        }

        $existingShipments = $this->shipmentRepository->findBy(['orderId' => $orderIds]);
        $orderIds = array_diff($orderIds, array_map(static function (Shipment $shipment): int {
            return $shipment->getOrderId();
        }, $existingShipments));

        return $this->orderRepository->findByIdsAndCarrierModuleName(\InPostInternational::MODULE_NAME, ...$orderIds);
    }

    private function createShipment(\Order $order): Shipment
    {
        $command = new CreateShipmentCommand();
        $this->commandBuilder->buildCommand($command, $order);
        $command->setOrderId((int) $order->id);

        return $this->bus->handle($command);
    }
}
