<?php

declare(strict_types=1);

namespace App\Module\Transport\Infrastructure\Controller;

use App\Module\Transport\Domain\Entity\Carrier;
use App\Module\Transport\Domain\Entity\CarrierPrice;
use App\Module\Transport\Domain\Repository\CarrierRepositoryInterface;
use App\Shared\Domain\Contract\SpreadsheetExporterInterface;
use DateTimeImmutable;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

/**
 * Export XLSX du tableau Prix d'un transporteur (M4, spec-back § 4.6 / spec-front
 * § « Onglet Prix »). Reproduit le tableau de consultation regroupe par type de
 * contenant (Fond Mouvant / Benne — colonnes du docx p.10).
 *
 * Controller Symfony custom (binaire de fichier, pas une representation Hydra).
 * `priority: 1` est OBLIGATOIRE : sans cela API Platform capterait
 * `/api/carriers/{id}/prices/export.xlsx` via ses routes generiques.
 *
 * Separation des responsabilites : le COMMENT (generation) est delegue au service
 * Shared {@see SpreadsheetExporterInterface} ; le QUOI (chargement du transporteur,
 * regroupement par contenant, mapping metier des colonnes) vit ICI.
 *
 * Adresses cross-module : les contrats Shared (ClientInterface / SupplierInterface
 * / SiteInterface) exposent volontairement le minimum (regle ABSOLUE n°1). Faute
 * d'acceder au detail postal d'une adresse Client/Fournisseur sans coupler au
 * module Commercial, les colonnes d'adresse identifient le point par le libelle
 * disponible : nom du site pour un Site, raison sociale du client/fournisseur pour
 * une adresse de livraison/approvisionnement.
 */
#[AsController]
final class CarrierPriceExportController
{
    /** Libelles d'affichage des enums (spec-front « Onglet Prix »). */
    private const array CONTAINER_LABELS = ['BENNE' => 'Benne', 'FOND_MOUVANT' => 'Fond Mouvant'];

    private const array PRICE_STATE_LABELS = [
        'EN_COURS'   => 'En cours',
        'VALIDE'     => 'Validé',
        'NON_VALIDE' => 'Non validé',
    ];

    public function __construct(
        #[Autowire(service: 'App\Module\Transport\Infrastructure\Doctrine\DoctrineCarrierRepository')]
        private readonly CarrierRepositoryInterface $repository,
        private readonly SpreadsheetExporterInterface $exporter,
    ) {}

    #[Route('/api/carriers/{id}/prices/export.xlsx', name: 'transport_carrier_prices_export_xlsx', requirements: ['id' => '\d+'], methods: ['GET'], priority: 1)]
    #[IsGranted('transport.carriers.view')]
    public function __invoke(int $id): Response
    {
        $carrier = $this->repository->findById($id);
        // Soft-delete jamais expose (comme CarrierProvider::provideItem) : 404.
        if (null === $carrier || null !== $carrier->getDeletedAt()) {
            throw new NotFoundHttpException('Transporteur introuvable.');
        }

        $binary = $this->exporter->export(
            'Prix transporteur',
            $this->buildHeaders(),
            $this->buildRows($carrier),
        );

        return $this->buildResponse($carrier, $binary);
    }

    /**
     * Colonnes du tableau Prix regroupe (spec-front « Onglet Prix » / docx p.10).
     *
     * @return list<string>
     */
    private function buildHeaders(): array
    {
        return [
            'Type de contenant',
            'Transporteurs',
            'Adresse APRO ou Adresse Sites',
            'Adresse livraisons',
            'Forfait €',
            'Tonne €',
            'Indexation',
            'État du prix',
        ];
    }

    /**
     * Lignes regroupees par type de contenant (Fond Mouvant / Benne). On trie les
     * prix par contenant puis position pour materialiser le regroupement.
     *
     * @return iterable<list<null|scalar>>
     */
    private function buildRows(Carrier $carrier): iterable
    {
        $prices = $carrier->getPrices()->toArray();
        usort(
            $prices,
            static fn (CarrierPrice $a, CarrierPrice $b): int => [$a->getContainerType(), $a->getPosition()]
                <=> [$b->getContainerType(), $b->getPosition()],
        );

        // Indexation : portee par le transporteur (RG-4.03), identique pour toutes
        // ses lignes de prix. Vide si non renseigne (spec-front).
        $indexation = $carrier->getIndexationRate() ?? '';

        foreach ($prices as $price) {
            $isForfait = 'FORFAIT' === $price->getPricingUnit();

            yield [
                self::CONTAINER_LABELS[$price->getContainerType()] ?? $price->getContainerType(),
                $carrier->getName(),
                $this->formatDeparture($price),
                $this->formatDelivery($price),
                $isForfait ? $price->getPrice() : '',
                $isForfait ? '' : $price->getPrice(),
                $indexation,
                self::PRICE_STATE_LABELS[$price->getPriceState()] ?? $price->getPriceState(),
            ];
        }
    }

    /**
     * Point de depart du prix (colonne « Adresse APRO ou Adresse Sites ») :
     *  - branche CLIENT  : le site de depart (un des 3 sites 86/17/82) ;
     *  - branche FOURNISSEUR : l'adresse d'approvisionnement, identifiee par la
     *    raison sociale du fournisseur (cf. note de classe sur les contrats Shared).
     */
    private function formatDeparture(CarrierPrice $price): string
    {
        if ('CLIENT' === $price->getDirection()) {
            return $price->getDepartureSite()?->getName() ?? '';
        }

        return $price->getSupplierSupplyAddress()?->getSupplier()?->getCompanyName() ?? '';
    }

    /**
     * Point de livraison du prix (colonne « Adresse livraisons ») :
     *  - branche CLIENT  : l'adresse de livraison, identifiee par la raison sociale
     *    du client ;
     *  - branche FOURNISSEUR : le site de livraison (un des 3 sites 86/17/82).
     */
    private function formatDelivery(CarrierPrice $price): string
    {
        if ('CLIENT' === $price->getDirection()) {
            return $price->getClientDeliveryAddress()?->getClient()?->getCompanyName() ?? '';
        }

        return $price->getDeliverySite()?->getName() ?? '';
    }

    private function buildResponse(Carrier $carrier, string $binary): Response
    {
        $filename = sprintf('prix-transporteur-%d-%s.xlsx', (int) $carrier->getId(), new DateTimeImmutable()->format('Ymd'));

        $response = new Response($binary);
        $response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
        $response->headers->set('Content-Disposition', sprintf('attachment; filename="%s"', $filename));

        return $response;
    }
}
