<?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\Api\Exception\ApiClientExceptionInterface;
use InPost\International\Api\Tracking\Response\TrackingDetailsResponse;
use InPost\International\Api\Tracking\TrackingApiClientInterface;
use InPost\International\Entity\Shipment;
use InPost\International\Shipment\Event\ShipmentStatusUpdatedEvent;
use InPost\International\Shipment\Exception\NoShipmentsFoundException;
use InPost\International\Shipment\Exception\ShipmentException;
use InPost\International\Shipment\Message\BulkUpdateShipmentStatusesCommand;
use InPost\International\Shipment\ShipmentRepositoryInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

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

final class BulkUpdateShipmentStatusesHandler
{
    /**
     * @var ShipmentRepositoryInterface
     */
    private $repository;

    /**
     * @var TrackingApiClientInterface
     */
    private $client;

    /**
     * @var EventDispatcherInterface
     */
    private $dispatcher;

    public function __construct(ShipmentRepositoryInterface $repository, TrackingApiClientInterface $client, EventDispatcherInterface $dispatcher)
    {
        $this->repository = $repository;
        $this->client = $client;
        $this->dispatcher = $dispatcher;
    }

    /**
     * @throws ShipmentException
     * @throws ApiClientExceptionInterface
     */
    public function __invoke(BulkUpdateShipmentStatusesCommand $command): void
    {
        $shipments = $this->repository->findBy([
            'id' => $command->getShipmentIds(),
        ]);

        if ([] === $shipments) {
            throw new NoShipmentsFoundException('Shipment with provided IDs do not exist.');
        }

        foreach (array_chunk($shipments, 10) as $batch) {
            $this->processBatch($batch);
        }
    }

    public function handle(BulkUpdateShipmentStatusesCommand $command): void
    {
        ($this)($command);
    }

    /**
     * @param Shipment[] $shipments
     */
    private function processBatch(array $shipments): void
    {
        $trackingNumbers = array_map(static function (Shipment $shipment): string {
            return $shipment->getTrackingNumber();
        }, $shipments);

        $trackingDetails = $this->client->getTrackingDetails(...$trackingNumbers);

        foreach ($shipments as $shipment) {
            $this->updateShipmentStatus($shipment, $trackingDetails);
        }
    }

    private function updateShipmentStatus(Shipment $shipment, TrackingDetailsResponse $trackingDetails): void
    {
        if (null === $parcel = $trackingDetails->getParcel($shipment->getTrackingNumber())) {
            return;
        }

        $previousStatus = $shipment->getStatus();
        $shipment->updateStatus($parcel);

        if ($previousStatus === $shipment->getStatus()) {
            return;
        }

        $this->repository->add($shipment);
        $this->dispatcher->dispatch(new ShipmentStatusUpdatedEvent($shipment, $previousStatus));
    }
}
