<?php

declare(strict_types=1);

namespace App\Tests\Module\Transport\Api;

use App\Module\Transport\Domain\Entity\Carrier;
use PhpOffice\PhpSpreadsheet\IOFactory;

/**
 * Tests fonctionnels de l'export XLSX du tableau Prix d'un transporteur (M4,
 * § 4.6 / spec-front « Onglet Prix »).
 *
 * Couvre : reponse 200 (Content-Type + Content-Disposition + en-tetes), rendu des
 * lignes regroupees par type de contenant (Benne / Fond Mouvant) avec ventilation
 * Forfait/Tonne, libelles d'etat FR, points de depart/livraison cross-module,
 * 404 sur transporteur inconnu, 403 sans transport.carriers.view, 401 anonyme.
 *
 * @internal
 */
final class CarrierPriceExportControllerTest extends AbstractCarrierApiTestCase
{
    private const string XLSX_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';

    public function testExportReturnsXlsxResponseWithAttachmentFilename(): void
    {
        $client  = $this->createAdminClient();
        $carrier = $this->seedCompleteCarrier('Price Alpha');

        $response = $client->request('GET', $this->exportUrl($carrier));

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

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

        $headerRow = $this->gridFromResponse($response->getContent())[0];
        self::assertSame('Type de contenant', $headerRow[0]);
        self::assertContains('Transporteurs', $headerRow);
        self::assertContains('Adresse APRO ou Adresse Sites', $headerRow);
        self::assertContains('Adresse livraisons', $headerRow);
        self::assertContains('Forfait €', $headerRow);
        self::assertContains('Tonne €', $headerRow);
        self::assertContains('Indexation', $headerRow);
        self::assertContains('État du prix', $headerRow);
    }

    /**
     * Le transporteur complet seede 2 prix : une branche CLIENT (Benne / Tonne /
     * 42.50 / Valide) et une branche FOURNISSEUR (Fond Mouvant / Forfait / 320.00 /
     * En cours). On verifie le regroupement par contenant, la ventilation
     * Forfait/Tonne, les libelles d'etat FR et les points de depart/livraison
     * cross-module (le prix CLIENT livre chez le client, le prix FOURNISSEUR part
     * de l'adresse du fournisseur).
     */
    public function testExportRendersGroupedPriceRows(): void
    {
        $client  = $this->createAdminClient();
        $carrier = $this->seedCompleteCarrier('Price Grouping');

        $grid = $this->gridFromResponse($client->request('GET', $this->exportUrl($carrier))->getContent());

        $benne = $this->rowForContainer($grid, 'Benne');
        self::assertNotNull($benne, 'Ligne « Benne » introuvable dans l\'export prix.');
        self::assertSame($carrier->getName(), $benne[1]);
        // Branche CLIENT : prix en Tonne (42.50 -> 42.5 apres typage numerique du
        // classeur), colonne Forfait vide, etat « Valide », livraison chez le client.
        self::assertEmpty($benne[4]);
        self::assertEqualsWithDelta(42.5, (float) $benne[5], 0.001);
        self::assertSame('Validé', $benne[7]);
        self::assertStringContainsString('TESTCARRIERREF CLI', (string) $benne[3]);

        $fondMouvant = $this->rowForContainer($grid, 'Fond Mouvant');
        self::assertNotNull($fondMouvant, 'Ligne « Fond Mouvant » introuvable dans l\'export prix.');
        // Branche FOURNISSEUR : prix au Forfait (320.00 -> 320), colonne Tonne vide,
        // etat « En cours », depart depuis l'adresse du fournisseur (APRO).
        self::assertEqualsWithDelta(320.0, (float) $fondMouvant[4], 0.001);
        self::assertEmpty($fondMouvant[5]);
        self::assertSame('En cours', $fondMouvant[7]);
        self::assertStringContainsString('TESTCARRIERREF FRN', (string) $fondMouvant[2]);
    }

    public function testNotFoundForUnknownCarrier(): void
    {
        $client = $this->createAdminClient();

        $client->request('GET', '/api/carriers/99999999/prices/export.xlsx');

        self::assertResponseStatusCodeSame(404);
    }

    public function testForbiddenWithoutCarriersViewPermission(): void
    {
        $carrier = $this->seedCompleteCarrier('Price Forbidden');

        $creds  = $this->createUserWithPermission('core.users.view');
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

        $client->request('GET', $this->exportUrl($carrier));

        self::assertResponseStatusCodeSame(403);
    }

    public function testUnauthorizedWhenAnonymous(): void
    {
        $carrier = $this->seedCompleteCarrier('Price Anonymous');

        $client = self::createClient();
        $client->request('GET', $this->exportUrl($carrier));

        self::assertResponseStatusCodeSame(401);
    }

    private function exportUrl(Carrier $carrier): string
    {
        return sprintf('/api/carriers/%d/prices/export.xlsx', (int) $carrier->getId());
    }

    /**
     * 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_price_export_test_');
        self::assertIsString($tmp);
        file_put_contents($tmp, $binary);

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

    /**
     * Renvoie la 1re ligne de donnees dont la colonne « Type de contenant »
     * (1re colonne) vaut $container, ou null.
     *
     * @param array<int, array<int, mixed>> $grid
     *
     * @return null|array<int, mixed>
     */
    private function rowForContainer(array $grid, string $container): ?array
    {
        foreach (array_slice($grid, 1) as $row) {
            if ((string) ($row[0] ?? '') === $container) {
                return $row;
            }
        }

        return null;
    }
}
