<?php

declare(strict_types=1);

namespace App\Tests\Module\Transport\Api;

use PhpOffice\PhpSpreadsheet\IOFactory;

/**
 * Tests fonctionnels de l'export XLSX du repertoire transporteurs (M4, § 4.6).
 * Jumeau du {@see \App\Tests\Module\Commercial\Api\SupplierExportControllerTest}.
 *
 * Couvre : reponse 200 (Content-Type + Content-Disposition + en-tetes), exclusion
 * des archives par defaut, respect du filtre ?search, peuplement des colonnes
 * QUALIMAT (statut + date de validite, RG-4.04), 403 sans transport.carriers.view,
 * 401 anonyme.
 *
 * @internal
 */
final class CarrierExportControllerTest extends AbstractCarrierApiTestCase
{
    private const string XLSX_MIME  = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
    private const string EXPORT_URL = '/api/carriers/export.xlsx';

    public function testExportReturnsXlsxResponseWithAttachmentFilename(): void
    {
        $client = $this->createAdminClient();
        $this->seedCarrier('Export Alpha');

        $response = $client->request('GET', self::EXPORT_URL);

        self::assertResponseIsSuccessful();
        $headers = $response->getHeaders(false);
        self::assertStringContainsString(self::XLSX_MIME, $headers['content-type'][0] ?? '');

        $disposition = $headers['content-disposition'][0] ?? '';
        self::assertStringContainsString('attachment; filename="repertoire-transporteurs-', $disposition);
        self::assertMatchesRegularExpression(
            '/filename="repertoire-transporteurs-\d{8}\.xlsx"/',
            $disposition,
        );

        // Le binaire est un XLSX relisible dont la 1re ligne porte les en-tetes.
        $headers = $this->gridFromResponse($response->getContent())[0];
        self::assertSame('Nom', $headers[0]);
        self::assertContains('Certification', $headers);
        self::assertContains('Statut QUALIMAT', $headers);
        self::assertContains('Date de validité', $headers);
        self::assertContains('Affrété', $headers);
        self::assertContains('Volume m³', $headers);
        self::assertContains('Date de création', $headers);
    }

    public function testExportExcludesArchivedByDefault(): void
    {
        $client = $this->createAdminClient();
        $this->seedCarrier('Active One');
        $this->seedCarrier('Archived One', true);

        $names = $this->carrierNames($client->request('GET', self::EXPORT_URL)->getContent());

        self::assertContains('ACTIVE ONE', $names);
        self::assertNotContains('ARCHIVED ONE', $names);
    }

    public function testExportRespectsSearchFilter(): void
    {
        $client = $this->createAdminClient();
        $this->seedCarrier('Searchable Alpha');
        $this->seedCarrier('Other Beta');

        $names = $this->carrierNames(
            $client->request('GET', self::EXPORT_URL.'?search=alpha')->getContent(),
        );

        self::assertContains('SEARCHABLE ALPHA', $names);
        self::assertNotContains('OTHER BETA', $names);
    }

    /**
     * Colonnes « Statut QUALIMAT » et « Date de validite » : alimentees par le
     * referentiel QUALIMAT lie (RG-4.04). Un transporteur complet seede un lien
     * QUALIMAT (statut « Valide », validite 31/12/2027).
     */
    public function testExportPopulatesQualimatColumns(): void
    {
        $client = $this->createAdminClient();
        $this->seedCompleteCarrier('Grelillier');

        $flat = $this->flatten($this->gridFromResponse($client->request('GET', self::EXPORT_URL)->getContent()));

        self::assertStringContainsString('QUALIMAT', $flat);
        self::assertStringContainsString('Valide', $flat);
        self::assertStringContainsString('31/12/2027', $flat);
    }

    public function testForbiddenWithoutCarriersViewPermission(): void
    {
        $creds  = $this->createUserWithPermission('core.users.view');
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

        $client->request('GET', self::EXPORT_URL);

        self::assertResponseStatusCodeSame(403);
    }

    public function testUnauthorizedWhenAnonymous(): void
    {
        $client = self::createClient();
        $client->request('GET', self::EXPORT_URL);

        self::assertResponseStatusCodeSame(401);
    }

    /**
     * Relit le binaire XLSX d'une reponse et renvoie la grille de cellules.
     *
     * @return array<int, array<int, mixed>>
     */
    private function gridFromResponse(string $binary): array
    {
        $tmp = tempnam(sys_get_temp_dir(), 'xlsx_carrier_export_test_');
        self::assertIsString($tmp);
        file_put_contents($tmp, $binary);

        try {
            return IOFactory::load($tmp)->getActiveSheet()->toArray();
        } finally {
            @unlink($tmp);
        }
    }

    /**
     * Extrait la colonne « Nom » (1re colonne) des lignes de donnees.
     *
     * @return list<string>
     */
    private function carrierNames(string $binary): array
    {
        $rows = array_slice($this->gridFromResponse($binary), 1); // saute l'en-tete

        return array_values(array_map(static fn (array $row): string => (string) ($row[0] ?? ''), $rows));
    }

    /**
     * Aplatit toute la grille en une chaine, pour les assertions de presence.
     *
     * @param array<int, array<int, mixed>> $grid
     */
    private function flatten(array $grid): string
    {
        return implode('|', array_map(
            static fn (array $row): string => implode('|', array_map(static fn ($cell): string => (string) $cell, $row)),
            $grid,
        ));
    }
}
