<?php

declare(strict_types=1);

namespace App\Module\Transport\Infrastructure\Controller;

use App\Module\Transport\Domain\Entity\Carrier;
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\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

/**
 * Export XLSX du repertoire transporteurs (M4, spec-back § 4.6). Jumeau des
 * controllers d'export SupplierExportController (M2) / ProviderExportController
 * (M3) — references en prose volontairement (pas de {@see} : un import
 * inter-module violerait la regle ABSOLUE n°1). Simplifie : pas de cloisonnement
 * par site (§ 2.3) ni de colonne gatee par une permission comptable.
 *
 * Controller Symfony custom (et non operation API Platform) car il produit un
 * binaire de fichier, pas une representation Hydra. `priority: 1` est OBLIGATOIRE
 * sur la route : sans cela API Platform capterait `/api/carriers/export.xlsx`
 * comme l'item `GET /api/carriers/{id}.{_format}` (id="export", _format="xlsx")
 * — cf. CLAUDE.md « controller custom sous /api ».
 *
 * Separation des responsabilites :
 *  - le COMMENT (generation du fichier) est delegue au service Shared
 *    {@see SpreadsheetExporterInterface} — generique, reutilisable, sans metier ;
 *  - le QUOI vit ICI : selection des transporteurs (MEMES filtres que
 *    `GET /api/carriers`, via {@see CarrierRepositoryInterface::createListQueryBuilder()}
 *    — l'export reflete exactement ce que l'utilisateur voit a l'ecran) et mapping
 *    metier des colonnes.
 */
#[AsController]
final class CarrierExportController
{
    public function __construct(
        #[Autowire(service: 'App\Module\Transport\Infrastructure\Doctrine\DoctrineCarrierRepository')]
        private readonly CarrierRepositoryInterface $repository,
        private readonly SpreadsheetExporterInterface $exporter,
    ) {}

    #[Route('/api/carriers/export.xlsx', name: 'transport_carriers_export_xlsx', methods: ['GET'], priority: 1)]
    #[IsGranted('transport.carriers.view')]
    public function __invoke(Request $request): Response
    {
        // Memes filtres que la vue liste (CarrierProvider) pour que l'export
        // reflete exactement ce que l'utilisateur voit a l'ecran :
        //  - includeArchived : reintegre les archives en plus des actifs ;
        //  - archivedOnly    : n'exporte QUE les archives (prioritaire sur
        //    includeArchived, aligne sur le provider — toggle « Voir les archives ») ;
        //  - search          : recherche fuzzy sur le nom ;
        //  - certificationType : filtre repetable (?certificationType[]=A&...).
        $includeArchived    = $this->readBool($request->query->get('includeArchived'));
        $archivedOnly       = $this->readBool($request->query->get('archivedOnly'));
        $search             = $request->query->getString('search') ?: null;
        $certificationTypes = $this->readStringList($request->query->all()['certificationType'] ?? []);

        /** @var list<Carrier> $carriers */
        $carriers = $this->repository
            ->createListQueryBuilder($includeArchived, $search, $certificationTypes, $archivedOnly)
            ->getQuery()
            ->getResult()
        ;

        $binary = $this->exporter->export(
            'Répertoire transporteurs',
            $this->buildHeaders(),
            $this->buildRows($carriers),
        );

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

    /**
     * Colonnes de l'export (spec § 4.6).
     *
     * @return list<string>
     */
    private function buildHeaders(): array
    {
        return [
            'Nom',
            'Certification',
            'Statut QUALIMAT',
            'Date de validité',
            'Affrété',
            'Volume m³',
            'Date de création',
        ];
    }

    /**
     * @param list<Carrier> $carriers
     *
     * @return iterable<list<null|scalar>>
     */
    private function buildRows(array $carriers): iterable
    {
        foreach ($carriers as $carrier) {
            // Statut / date de validite proviennent du referentiel QUALIMAT lie
            // (RG-4.04), deja fetch-joine par le repository (anti N+1, § 2.11).
            $qualimat = $carrier->getQualimatCarrier();

            yield [
                $carrier->getName(),
                $carrier->getCertificationType() ?? '',
                $qualimat?->getStatus() ?? '',
                $qualimat?->getValidityDate()?->format('d/m/Y') ?? '',
                $carrier->isChartered() ? 'Oui' : 'Non',
                $carrier->getVolumeM3() ?? '',
                $carrier->getCreatedAt()?->format('d/m/Y'),
            ];
        }
    }

    private function buildResponse(string $binary): Response
    {
        $filename = sprintf('repertoire-transporteurs-%s.xlsx', 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;
    }

    /**
     * Lit un flag booleen issu des query params. Accepte true / "true" / "1".
     * Aligne sur CarrierProvider pour un comportement identique a la liste.
     */
    private function readBool(mixed $raw): bool
    {
        return is_string($raw) && in_array(strtolower($raw), ['true', '1'], true);
    }

    /**
     * Normalise un filtre en liste de chaines (valeur unique ou liste).
     * Aligne sur CarrierProvider pour un comportement identique a la liste.
     *
     * @return list<string>
     */
    private function readStringList(mixed $raw): array
    {
        $values = is_array($raw) ? $raw : [$raw];

        $out = [];
        foreach ($values as $value) {
            if (is_string($value) && '' !== trim($value)) {
                $out[] = trim($value);
            }
        }

        return $out;
    }
}
