<?php
use PHPUnit\Framework\TestCase;
require_once __DIR__ . '/../src/db.php';
require_once __DIR__ . '/../src/filieres.php';
require_once __DIR__ . '/../src/candidatures.php';
final class CandidaturesTest extends TestCase {
    private function donneesValides(): array {
        return ['nom'=>'Koné','prenoms'=>'Awa','date_naissance'=>'2007-03-12','lieu_naissance'=>'Korhogo',
        'sexe'=>'F','nationalite'=>'Ivoirienne','telephone'=>'0102030405','email'=>'','ville'=>'Korhogo',
        'tuteur_nom'=>'Koné Issa','tuteur_telephone'=>'0506070809','bac_serie'=>'D','bac_annee'=>'2026',
        'etablissement_origine'=>'Lycée Moderne Korhogo','filiere1'=>'INFO','filiere2'=>'GES'];
    }
    public function testDonneesValidesPassent(): void {
        $this->assertSame([], valider_candidature($this->donneesValides()));
    }
    public function testChampsObligatoiresManquants(): void {
        $e = valider_candidature([]);
        $this->assertArrayHasKey('nom', $e); $this->assertArrayHasKey('telephone', $e); $this->assertArrayHasKey('filiere1', $e);
    }
    public function testFiliereInconnueRefusee(): void {
        $d = $this->donneesValides(); $d['filiere1'] = 'XXX';
        $this->assertArrayHasKey('filiere1', valider_candidature($d));
    }
    public function testTelephoneInvalideRefuse(): void {
        $d = $this->donneesValides(); $d['telephone'] = 'abc';
        $this->assertArrayHasKey('telephone', valider_candidature($d));
    }
    // Revue Task 5 — fix 2 : valeurs non scalaires (ex. nom[]=x dans le POST) sans erreur fatale
    public function testValeurNonChaineRefuseeSansErreurFatale(): void {
        $d = $this->donneesValides(); $d['nom'] = ['x'];
        $e = valider_candidature($d);
        $this->assertSame('Valeur invalide.', $e['nom'] ?? null);
    }
    public function testTelephoneTableauRefuseSansErreurFatale(): void {
        $d = $this->donneesValides(); $d['telephone'] = ['0102030405'];
        $this->assertArrayHasKey('telephone', valider_candidature($d));
    }
    // Revue Task 5 — fix 3 : dates de naissance implausibles
    public function testDateNaissanceDebordementCalendaireRefusee(): void {
        $d = $this->donneesValides(); $d['date_naissance'] = '2007-02-30';
        $e = valider_candidature($d);
        $this->assertSame('Date de naissance invalide.', $e['date_naissance'] ?? null);
    }
    public function testDateNaissanceFutureRefusee(): void {
        $d = $this->donneesValides(); $d['date_naissance'] = '2099-01-01';
        $this->assertArrayHasKey('date_naissance', valider_candidature($d));
    }
    public function testDateNaissanceAvant1940Refusee(): void {
        $d = $this->donneesValides(); $d['date_naissance'] = '1939-12-31';
        $this->assertArrayHasKey('date_naissance', valider_candidature($d));
    }
    public function testDateNaissanceMauvaisFormatRefusee(): void {
        $d = $this->donneesValides(); $d['date_naissance'] = '12/03/2007';
        $this->assertArrayHasKey('date_naissance', valider_candidature($d));
    }
    // Revue Task 5 — fix 4 : longueurs maximales alignées sur schema.sql
    public function testNomTropLongRefuse(): void {
        $d = $this->donneesValides(); $d['nom'] = str_repeat('A', 300);
        $e = valider_candidature($d);
        $this->assertSame('Texte trop long (max 100 caractères).', $e['nom'] ?? null);
    }
    // Revue Task 5 — fix 5 : le téléphone compte les chiffres, pas les caractères
    public function testTelephoneEspacesSansChiffresSuffisantsRefuse(): void {
        $d = $this->donneesValides(); $d['telephone'] = '1       ';
        $this->assertArrayHasKey('telephone', valider_candidature($d));
    }
    public function testTelephoneAvecEspacesAccepte(): void {
        $d = $this->donneesValides(); $d['telephone'] = '01 02 03 04 05';
        $this->assertSame([], valider_candidature($d));
    }
    // Revue Task 5 — fix 6 : bac_annee = année seule sur 4 chiffres
    public function testBacAnneePlageScolaireRefusee(): void {
        $d = $this->donneesValides(); $d['bac_annee'] = '2024-2025';
        $e = valider_candidature($d);
        $this->assertSame('Année du BAC invalide (ex. 2026).', $e['bac_annee'] ?? null);
    }
    // Revue Task 5 — fix 10 : message explicite pour le sexe
    public function testSexeInvalideRefuse(): void {
        $d = $this->donneesValides(); $d['sexe'] = 'X';
        $e = valider_candidature($d);
        $this->assertSame('Sexe invalide (M ou F attendu).', $e['sexe'] ?? null);
    }
    // Revue Task 5 — fix 9 : branches non couvertes
    public function testTuteurTelephoneInvalideRefuse(): void {
        $d = $this->donneesValides(); $d['tuteur_telephone'] = 'abc';
        $this->assertArrayHasKey('tuteur_telephone', valider_candidature($d));
    }
    public function testEmailInvalideRefuse(): void {
        $d = $this->donneesValides(); $d['email'] = 'pas-un-email';
        $this->assertArrayHasKey('email', valider_candidature($d));
    }
    public function testFiliere2InconnueRefusee(): void {
        $d = $this->donneesValides(); $d['filiere2'] = 'XXX';
        $this->assertArrayHasKey('filiere2', valider_candidature($d));
    }
    public function testNumeroDossierSequentiel(): void {
        $pdo = db_test();
        $this->assertSame('ISIT-2026-0001', generer_numero_dossier($pdo, 2026));
        enregistrer_candidature($pdo, $this->donneesValides(), 2026);
        $this->assertSame('ISIT-2026-0002', generer_numero_dossier($pdo, 2026));
    }
    // Revue Task 5 — fix 1 : la numérotation ne doit jamais recollisionner après une suppression
    public function testNumeroDossierApresSuppressionSansCollision(): void {
        $pdo = db_test();
        enregistrer_candidature($pdo, $this->donneesValides(), 2026); // 0001
        enregistrer_candidature($pdo, $this->donneesValides(), 2026); // 0002
        enregistrer_candidature($pdo, $this->donneesValides(), 2026); // 0003
        $pdo->exec("DELETE FROM candidatures WHERE numero = 'ISIT-2026-0002'");
        $this->assertSame('ISIT-2026-0004', generer_numero_dossier($pdo, 2026));
    }
    public function testEnregistrementRetourneIdEtNumero(): void {
        $pdo = db_test();
        $r = enregistrer_candidature($pdo, $this->donneesValides(), 2026);
        $this->assertSame('ISIT-2026-0001', $r['numero']);
        $row = $pdo->query('SELECT * FROM candidatures')->fetch();
        $this->assertSame('Koné', $row['nom']); $this->assertSame('nouveau', $row['statut']);
        // Revue Task 5 — fix 9 : email vide stocké NULL, filiere2 renseignée stockée telle quelle
        $this->assertNull($row['email']);
        $this->assertSame('GES', $row['filiere2']);
    }
    // Revue Task 5 — fix 8 : valeurs trimées avant insertion, filiere2 vide stockée NULL
    public function testEnregistrementTrimEtFiliere2VideDevientNull(): void {
        $pdo = db_test();
        $d = $this->donneesValides();
        $d['nom'] = '   Koné   ';
        $d['filiere2'] = '';
        enregistrer_candidature($pdo, $d, 2026);
        $row = $pdo->query('SELECT * FROM candidatures')->fetch();
        $this->assertSame('Koné', $row['nom']);
        $this->assertNull($row['filiere2']);
    }
    // Revue Task 5 — fix 7 : collision de numéro (concurrence) => nouvelle tentative avec numéro frais.
    // Simulation déterministe : un déclencheur insère la ligne du « gagnant concurrent » puis fait
    // échouer l'INSERT courant avec le même SQLSTATE 23000 qu'une vraie violation UNIQUE.
    // ---- Revue Task 10 : filtrer_candidatures() (liste admin) ----
    /** Trois candidatures : Koné/INFO (0001), Traoré/GES statut traite (0002), Yéo/FIN (0003). */
    private function troisCandidatures(PDO $pdo): void {
        $d2 = $this->donneesValides();
        $d2['nom'] = 'Traoré'; $d2['prenoms'] = 'Moussa'; $d2['telephone'] = '0708091011'; $d2['filiere1'] = 'GES';
        $d3 = $this->donneesValides();
        $d3['nom'] = 'Yéo'; $d3['prenoms'] = 'Fatou'; $d3['telephone'] = '0504030201'; $d3['filiere1'] = 'FIN';
        enregistrer_candidature($pdo, $this->donneesValides(), 2026); // 0001
        enregistrer_candidature($pdo, $d2, 2026);                     // 0002
        enregistrer_candidature($pdo, $d3, 2026);                     // 0003
        $pdo->exec("UPDATE candidatures SET statut = 'traite' WHERE numero = 'ISIT-2026-0002'");
    }
    /** @param array{filiere?:string,statut?:string,q?:string} $filtres @return string[] numéros retournés */
    private function numerosFiltres(PDO $pdo, array $filtres): array {
        $filtres += ['filiere' => '', 'statut' => '', 'q' => ''];
        return array_column(filtrer_candidatures($pdo, $filtres), 'numero');
    }
    public function testFiltrerSansFiltreToutTrieDuPlusRecent(): void {
        $pdo = db_test(); $this->troisCandidatures($pdo);
        $this->assertSame(['ISIT-2026-0003', 'ISIT-2026-0002', 'ISIT-2026-0001'], $this->numerosFiltres($pdo, []));
    }
    public function testFiltrerQPourcentEstLitteral(): void {
        $pdo = db_test(); $this->troisCandidatures($pdo);
        $this->assertSame([], $this->numerosFiltres($pdo, ['q' => '%'])); // pas un joker : 0 résultat, pas 3
    }
    public function testFiltrerQSoulignementEstLitteral(): void {
        $pdo = db_test(); $this->troisCandidatures($pdo);
        $this->assertSame([], $this->numerosFiltres($pdo, ['q' => '_']));
    }
    public function testFiltrerQJokerAuMilieuEstLitteral(): void {
        $pdo = db_test(); $this->troisCandidatures($pdo);
        // Sans échappement, « Tra% » deviendrait %Tra%% et matcherait Traoré.
        $this->assertSame([], $this->numerosFiltres($pdo, ['q' => 'Tra%']));
        $this->assertSame([], $this->numerosFiltres($pdo, ['q' => '\\']));
    }
    public function testFiltrerQPartielNomEtTelephone(): void {
        $pdo = db_test(); $this->troisCandidatures($pdo);
        $this->assertSame(['ISIT-2026-0002'], $this->numerosFiltres($pdo, ['q' => 'Traor']));
        $this->assertSame(['ISIT-2026-0003'], $this->numerosFiltres($pdo, ['q' => '0504']));
    }
    public function testFiltrerParFiliere(): void {
        $pdo = db_test(); $this->troisCandidatures($pdo);
        $this->assertSame(['ISIT-2026-0001'], $this->numerosFiltres($pdo, ['filiere' => 'INFO']));
    }
    public function testFiltrerParStatut(): void {
        $pdo = db_test(); $this->troisCandidatures($pdo);
        $this->assertSame(['ISIT-2026-0002'], $this->numerosFiltres($pdo, ['statut' => 'traite']));
    }
    public function testFiltrerCombine(): void {
        $pdo = db_test(); $this->troisCandidatures($pdo);
        $this->assertSame(['ISIT-2026-0002'], $this->numerosFiltres($pdo, ['filiere' => 'GES', 'statut' => 'traite', 'q' => 'Mouss']));
        $this->assertSame([], $this->numerosFiltres($pdo, ['filiere' => 'INFO', 'statut' => 'traite']));
    }
    public function testCollisionNumeroRelanceEtAboutit(): void {
        $pdo = db_test();
        $pdo->exec("CREATE TRIGGER simule_concurrence BEFORE INSERT ON candidatures
            WHEN NEW.numero = 'ISIT-2026-0001'
            BEGIN
                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', 'Traoré', 'Moussa', '2006-05-01', 'Korhogo', 'M', 'Ivoirienne',
                    '0708091011', 'Korhogo', 'Traoré Ali', '0102030405', 'C', '2025', 'Lycée Houphouët', 'GES');
                SELECT RAISE(FAIL, 'UNIQUE constraint failed: candidatures.numero (simulation concurrence)');
            END");
        $r = enregistrer_candidature($pdo, $this->donneesValides(), 2026);
        $this->assertSame('ISIT-2026-0002', $r['numero']);
        $rows = $pdo->query('SELECT numero, nom FROM candidatures ORDER BY numero')->fetchAll();
        $this->assertCount(2, $rows);
        $this->assertSame('Traoré', $rows[0]['nom']); // le « concurrent » a gardé 0001
        $this->assertSame('Koné', $rows[1]['nom']);   // la nôtre a été réenregistrée en 0002
    }
}
