<?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\Shipment\ShipmentsApiClientInterface;
use InPost\International\Entity\Shipment;
use InPost\International\File\Base64EncodedFile;
use InPost\International\File\Base64EncodedFilesArchive;
use InPost\International\File\StreamableInterface;
use InPost\International\Shipment\Event\LabelPrintedEvent;
use InPost\International\Shipment\Exception\NoShipmentsFoundException;
use InPost\International\Shipment\Exception\ShipmentException;
use InPost\International\Shipment\Message\BulkPrintLabelsCommand;
use InPost\International\Shipment\ShipmentRepositoryInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

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

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

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

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

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

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

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

        if (1 === count($shipments)) {
            return $this->printSingleLabel($shipments[0]);
        }

        $labels = $this->getLabels($shipments);

        return new Base64EncodedFilesArchive($labels, 'labels.zip');
    }

    public function handle(BulkPrintLabelsCommand $command): StreamableInterface
    {
        return ($this)($command);
    }

    /**
     * @param Shipment[] $shipments
     *
     * @return iterable<string, string>
     */
    private function getLabels(array $shipments): iterable
    {
        foreach ($shipments as $shipment) {
            $content = $this->fetchLabelContent($shipment);
            $filename = $this->getFilename($shipment);

            yield $filename => $content;
        }
    }

    private function printSingleLabel(Shipment $shipment): StreamableInterface
    {
        $label = $this->fetchLabelContent($shipment);
        $filename = $this->getFilename($shipment);

        return new Base64EncodedFile($label, 'application/pdf', $filename);
    }

    private function fetchLabelContent(Shipment $shipment): string
    {
        $label = $this->client->getShipmentLabel($shipment->getUuid());
        $this->dispatcher->dispatch(new LabelPrintedEvent($shipment));

        return $label->getLabel()->getContent();
    }

    private function getFilename(Shipment $shipment): string
    {
        return sprintf('%s.pdf', $shipment->getTrackingNumber());
    }
}
