<?php
use PHPUnit\Framework\TestCase;
require_once __DIR__ . '/../src/db.php';
require_once __DIR__ . '/../src/pieces.php';
final class PiecesTest extends TestCase {
    private string $dirUploads;
    /** @var string[] */
    private array $fichiersTemp = [];

    protected function setUp(): void {
        $this->dirUploads = sys_get_temp_dir() . '/isit_uploads_' . bin2hex(random_bytes(6));
        mkdir($this->dirUploads, 0755, true);
    }
    protected function tearDown(): void {
        self::supprimerDossier($this->dirUploads);
        foreach ($this->fichiersTemp as $f) @unlink($f);
        $this->fichiersTemp = [];
    }
    private static function supprimerDossier(string $dir): void {
        if (!is_dir($dir)) return;
        foreach (scandir($dir) ?: [] as $e) {
            if ($e === '.' || $e === '..') continue;
            $p = "$dir/$e";
            is_dir($p) ? self::supprimerDossier($p) : @unlink($p);
        }
        @rmdir($dir);
    }
    /** Fichier temporaire réel dont le contenu est détecté comme PDF par fileinfo. */
    private function fichierTemp(string $contenu = "%PDF-1.4\n%simulation\n"): string {
        $f = tempnam(sys_get_temp_dir(), 'piece');
        file_put_contents($f, $contenu);
        $this->fichiersTemp[] = $f;
        return $f;
    }
    /** Entrée $_FILES simulée, complète (name, type, tmp_name, error, size). */
    private function fichier(string $nom = 'mon relevé.pdf', int $erreur = UPLOAD_ERR_OK): array {
        if ($erreur !== UPLOAD_ERR_OK) // PHP réel : pas de fichier temporaire quand l'upload a échoué
            return ['name' => $erreur === UPLOAD_ERR_NO_FILE ? '' : $nom, 'type' => '', 'tmp_name' => '', 'error' => $erreur, 'size' => 0];
        $tmp = $this->fichierTemp();
        return ['name' => $nom, 'type' => 'application/pdf', 'tmp_name' => $tmp, 'error' => UPLOAD_ERR_OK, 'size' => (int)filesize($tmp)];
    }
    private function candidatureId(PDO $pdo): int {
        $pdo->exec("INSERT INTO candidatures (numero, nom, prenoms, date_naissance, lieu_naissance, sexe,
            nationalite, telephone, ville, tuteur_nom, tuteur_telephone, bac_serie, bac_annee,
            etablissement_origine, filiere1)
            VALUES ('ISIT-2026-0001','Koné','Awa','2007-03-12','Korhogo','F','Ivoirienne','0102030405',
            'Korhogo','Koné Issa','0506070809','D','2026','Lycée Moderne Korhogo','INFO')");
        return (int)$pdo->lastInsertId();
    }
    private function nbLignes(PDO $pdo): int {
        return (int)$pdo->query('SELECT COUNT(*) FROM pieces')->fetchColumn();
    }
    /** @return string[] */
    private function fichiersStockes(): array {
        return glob($this->dirUploads . '/pieces/*') ?: [];
    }

    public function testExtensionAutorisee(): void {
        $this->assertNull(valider_piece_meta('photo.jpg', 100_000));
        $this->assertNull(valider_piece_meta('releve.PDF', 100_000));
    }
    public function testExtensionInterdite(): void {
        $this->assertNotNull(valider_piece_meta('script.php', 100));
        $this->assertNotNull(valider_piece_meta('virus.exe', 100));
    }
    public function testTropVolumineux(): void {
        $this->assertNotNull(valider_piece_meta('photo.jpg', 6 * 1024 * 1024));
    }
    public function testNomStockageAleatoireAvecExtension(): void {
        $n = nom_stockage('mon relevé.pdf');
        $this->assertMatchesRegularExpression('/^[a-f0-9]{32}\.pdf$/', $n);
        $this->assertNotSame($n, nom_stockage('mon relevé.pdf'));
    }
    // Revue Task 6 — fix 4 : nom limité à 255 caractères (nom_original est un VARCHAR(255))
    public function testNomFichierTropLong(): void {
        $this->assertSame('Nom de fichier trop long.', valider_piece_meta(str_repeat('a', 300) . '.pdf', 100));
        $pdo = db_test(); $id = $this->candidatureId($pdo);
        $r = enregistrer_piece($pdo, $id, 'releve_bac', $this->fichier(str_repeat('a', 300) . '.pdf'), $this->dirUploads, 'rename');
        $this->assertSame('Nom de fichier trop long.', $r);
        $this->assertSame(0, $this->nbLignes($pdo));
    }
    // Revue Task 6 — verrou de la borne : 5 Mo pile accepté, un octet de plus refusé
    public function testTailleLimiteExacte(): void {
        $this->assertNull(valider_piece_meta('photo.jpg', PIECES_MAX));
        $this->assertSame('Fichier trop volumineux (5 Mo maximum).', valider_piece_meta('photo.jpg', PIECES_MAX + 1));
    }
    // Revue Task 6 — fix 1 : $_FILES['error'] examiné avant toute autre validation
    public function testPieceAbsenteRetourneNullSansLigne(): void {
        $pdo = db_test(); $id = $this->candidatureId($pdo);
        $r = enregistrer_piece($pdo, $id, 'photo', $this->fichier('', UPLOAD_ERR_NO_FILE), $this->dirUploads, 'rename');
        $this->assertNull($r);
        $this->assertSame(0, $this->nbLignes($pdo));
        $this->assertSame([], $this->fichiersStockes());
    }
    public function testDepassementIniSizeMessageVolumineux(): void {
        $pdo = db_test(); $id = $this->candidatureId($pdo);
        $r = enregistrer_piece($pdo, $id, 'photo', $this->fichier('grand.jpg', UPLOAD_ERR_INI_SIZE), $this->dirUploads, 'rename');
        $this->assertSame('Fichier trop volumineux (5 Mo maximum).', $r);
        $this->assertSame(0, $this->nbLignes($pdo));
    }
    public function testUploadPartielMessageGenerique(): void {
        $pdo = db_test(); $id = $this->candidatureId($pdo);
        $r = enregistrer_piece($pdo, $id, 'photo', $this->fichier('photo.jpg', UPLOAD_ERR_PARTIAL), $this->dirUploads, 'rename');
        $this->assertSame('Le téléversement a échoué. Veuillez réessayer.', $r);
        $this->assertSame(0, $this->nbLignes($pdo));
    }
    // Revue Task 6 — fix 3 : vocabulaire des types de pièces verrouillé
    public function testTypeInconnuRefuse(): void {
        $pdo = db_test(); $id = $this->candidatureId($pdo);
        $r = enregistrer_piece($pdo, $id, 'cv', $this->fichier(), $this->dirUploads, 'rename');
        $this->assertSame('Type de pièce inconnu.', $r);
        $this->assertSame(0, $this->nbLignes($pdo));
        $this->assertSame([], $this->fichiersStockes());
    }
    // Revue Task 6 — la branche « validation échouée » ne stocke rien (ni fichier ni ligne)
    public function testExtensionInterditeNeStockeRien(): void {
        $pdo = db_test(); $id = $this->candidatureId($pdo);
        $r = enregistrer_piece($pdo, $id, 'photo', $this->fichier('x.exe'), $this->dirUploads, 'rename');
        $this->assertSame('Format accepté : PDF, JPG ou PNG.', $r);
        $this->assertSame(0, $this->nbLignes($pdo));
        $this->assertSame([], $this->fichiersStockes());
    }
    // Revue Task 6 — fix 5 : parcours nominal complet via le point d'injection $deplacer
    public function testEnregistrementNominal(): void {
        $pdo = db_test(); $id = $this->candidatureId($pdo);
        $r = enregistrer_piece($pdo, $id, 'releve_bac', $this->fichier('mon relevé.pdf'), $this->dirUploads, 'rename');
        $this->assertNull($r);
        $stockes = $this->fichiersStockes();
        $this->assertCount(1, $stockes);
        $this->assertMatchesRegularExpression('/^[a-f0-9]{32}\.pdf$/', basename($stockes[0]));
        $row = $pdo->query('SELECT * FROM pieces')->fetch();
        $this->assertSame($id, (int)$row['candidature_id']);
        $this->assertSame('releve_bac', $row['type']);
        $this->assertSame('mon relevé.pdf', $row['nom_original']);
        $this->assertSame(basename($stockes[0]), $row['nom_stockage']);
    }
    // Revue Task 6 — fix 2 : si l'INSERT échoue, le fichier déjà déplacé est supprimé (pas d'orphelin)
    public function testInsertionEchoueeNettoieLeFichier(): void {
        $pdo = db_test(); // aucune candidature : la clé étrangère fera échouer l'INSERT
        $ancienLog = ini_get('error_log');
        ini_set('error_log', $this->dirUploads . '/php-error.log'); // absorbe le error_log attendu
        try {
            $r = enregistrer_piece($pdo, 999, 'photo', $this->fichier('photo.pdf'), $this->dirUploads, 'rename');
        } finally {
            ini_set('error_log', (string)$ancienLog);
        }
        $this->assertSame("Échec de l'enregistrement du fichier.", $r);
        $this->assertSame([], $this->fichiersStockes());
        $this->assertSame(0, $this->nbLignes($pdo));
    }
}
