import { describe, expect, it } from 'vitest';
import {
    dateNaissanceRequise,
    derniereDateNaissanceIso,
    estIdentifieeParCin,
    identifiantTransportable,
    isoDateNaissance,
    motifIndisponibilite,
    motifIndisponibiliteHorsDate,
    type FicheTej,
    type TejEcritureProp,
} from './tej';

/** Repli du catalogue : ces écrans passent toujours un libellé par défaut. */
const t = (_namespace: string, key: string, fallback?: string) => fallback ?? key;

const ECRITURE_OUVERTE: TejEcritureProp = { ecriture_active: true, raison_indisponible: null };
const ECRITURE_FERMEE: TejEcritureProp = {
    ecriture_active: false,
    raison_indisponible: 'La connexion à TEJ est désactivée côté serveur (TEJ_ENABLED). Aucun envoi ne peut partir.',
};

/**
 * Depuis le découplage du 2026-08-06, c'est `blocage_code` qui décide — plus la
 * phrase. Les `blocage` restent recopiés de `TejBeneficiaireService` pour que les
 * cas soient lisibles, mais ils ne pilotent plus rien : ce sont
 * les phrases réelles que le pré-vol renvoie, et c'est sur elles que l'écran doit
 * savoir distinguer ce qu'il peut lever de ce qu'il ne peut pas.
 */
const BLOCAGE_DATE =
    "TEJ exige la date de naissance pour un tiers identifié par son CIN, et cette fiche n'en porte pas " +
    "(elle est antérieure à la colonne). Indiquez-la au moment de l'envoi : sans elle, la fiche partirait " +
    "sans date et TEJ l'accepterait sans rien signaler.";
const BLOCAGE_EMAIL =
    'TEJ exige une adresse e-mail valide sur la fiche du tiers, et ne la vérifie que côté formulaire : ' +
    "une adresse absente ou mal formée serait enregistrée telle quelle. Complétez l'e-mail de cette fiche avant de l'envoyer.";
const BLOCAGE_CIN_MAL_FORME =
    "Le CIN de cette fiche n'a pas la forme attendue par TEJ (8 chiffres). Corrigez-le sur la fiche : " +
    'un identifiant erroné crée une fiche chez un autre tiers, sans erreur.';

const fiche = (surcharge: Partial<FicheTej> = {}): FicheTej => ({
    external_id: null,
    synced_at: null,
    type_identifier: 2,
    identifiant: '11041512',
    envoyable: false,
    blocage: BLOCAGE_DATE,
    blocage_code: 'date_naissance_requise',
    ...surcharge,
});

describe('dateNaissanceRequise', () => {
    it('reconnaît une fiche CIN à qui il ne manque que la date', () => {
        expect(dateNaissanceRequise(fiche())).toBe(true);
    });

    it("ne s'applique pas à un matricule fiscal — TEJ ne lui demande pas de date", () => {
        expect(dateNaissanceRequise(fiche({ type_identifier: 1, identifiant: '1184751K' }))).toBe(false);
    });

    it("ne s'applique pas à une fiche déjà envoyable", () => {
        expect(dateNaissanceRequise(fiche({ envoyable: true, blocage: null, blocage_code: null }))).toBe(false);
    });

    it("refuse quand l'identifiant est absent : le contrôle de forme a échoué AVANT la date", () => {
        expect(dateNaissanceRequise(fiche({ identifiant: null, blocage: BLOCAGE_CIN_MAL_FORME, blocage_code: 'autre' }))).toBe(false);
    });

    it("refuse quand le blocage est l'e-mail, seul contrôle intercalé avant la date", () => {
        expect(dateNaissanceRequise(fiche({ blocage: BLOCAGE_EMAIL, blocage_code: 'autre' }))).toBe(false);
    });

    it('refuse un état TEJ absent ou un type non qualifié', () => {
        expect(dateNaissanceRequise(null)).toBe(false);
        expect(dateNaissanceRequise(undefined)).toBe(false);
        expect(dateNaissanceRequise(fiche({ type_identifier: null }))).toBe(false);
    });
});

describe('motifIndisponibiliteHorsDate', () => {
    it('rend le bouton cliquable pour une fiche CIN sans date — le dialogue la demande', () => {
        expect(motifIndisponibilite(fiche(), ECRITURE_OUVERTE, t)).toBe(BLOCAGE_DATE);
        expect(motifIndisponibiliteHorsDate(fiche(), ECRITURE_OUVERTE, t)).toBeNull();
    });

    it('garde le verrou serveur au-dessus de tout : il ne se lève pas au clavier', () => {
        expect(motifIndisponibiliteHorsDate(fiche(), ECRITURE_FERMEE, t)).toBe(ECRITURE_FERMEE.raison_indisponible);
    });

    it('laisse passer les blocages que la saisie ne répare pas', () => {
        expect(motifIndisponibiliteHorsDate(fiche({ blocage: BLOCAGE_EMAIL, blocage_code: 'autre' }), ECRITURE_OUVERTE, t)).toBe(BLOCAGE_EMAIL);
        expect(
            motifIndisponibiliteHorsDate(fiche({ identifiant: null, blocage: BLOCAGE_CIN_MAL_FORME, blocage_code: 'autre' }), ECRITURE_OUVERTE, t),
        ).toBe(BLOCAGE_CIN_MAL_FORME);
    });

    it('signale toujours un état TEJ non fourni par le serveur', () => {
        expect(motifIndisponibiliteHorsDate(undefined, ECRITURE_OUVERTE, t)).toContain("n'a pas été fourni");
    });

    it('reste nul pour une fiche complète', () => {
        expect(motifIndisponibiliteHorsDate(fiche({ envoyable: true, blocage: null, blocage_code: null }), ECRITURE_OUVERTE, t)).toBeNull();
    });
});

describe('isoDateNaissance', () => {
    it('garde une date déjà ISO', () => {
        expect(isoDateNaissance('1985-03-07')).toBe('1985-03-07');
    });

    it('tronque un horodatage complet sans changer de jour', () => {
        expect(isoDateNaissance('1985-03-07T00:00:00.000000Z')).toBe('1985-03-07');
        expect(isoDateNaissance('1985-03-07 00:00:00')).toBe('1985-03-07');
    });

    it('convertit la forme TEJ jj/mm/aaaa', () => {
        expect(isoDateNaissance('07/03/1985')).toBe('1985-03-07');
    });

    it('rend une chaîne vide plutôt que de deviner', () => {
        expect(isoDateNaissance(null)).toBe('');
        expect(isoDateNaissance(undefined)).toBe('');
        expect(isoDateNaissance('')).toBe('');
        expect(isoDateNaissance('7 mars 1985')).toBe('');
        expect(isoDateNaissance('1985-3-7')).toBe('');
    });
});

/**
 * Ces cas SONT la règle de `BeneficiaireRequest::identifieeParUnCin()`, rejouée
 * sur l'écran. Elle décide de deux choses à la fois — l'affichage du champ et
 * son obligation côté serveur — donc une divergence produirait le pire des
 * résultats : un formulaire qui cache une donnée que le serveur exige.
 */
describe('estIdentifieeParCin', () => {
    it("reconnaît la fiche au seul CIN — c'est elle que TEJ identifie en typeIdentifier 2", () => {
        expect(estIdentifieeParCin('', '11041512')).toBe(true);
        expect(estIdentifieeParCin(null, '11041512')).toBe(true);
        expect(estIdentifieeParCin(undefined, '11041512')).toBe(true);
    });

    it('laisse le matricule fiscal PRIMER, même sur une personne physique qui en porte un', () => {
        // Le repli de `typeIdentifierPour()` : matricule d'abord, CIN à défaut.
        // Cette fiche part en typeIdentifier 1, sans `birthDate` — lui réclamer
        // la date bloquerait une création parfaitement légitime.
        expect(estIdentifieeParCin('1184751K', '11041512')).toBe(false);
        expect(estIdentifieeParCin('1184751K', '')).toBe(false);
    });

    it('ne réclame rien à une fiche sans aucune identité fiscale', () => {
        expect(estIdentifieeParCin('', '')).toBe(false);
        expect(estIdentifieeParCin(null, null)).toBe(false);
    });

    it('ignore les espaces des deux côtés, comme blank()/filled() côté serveur', () => {
        expect(estIdentifieeParCin('   ', '11041512')).toBe(true);
        expect(estIdentifieeParCin('', '   ')).toBe(false);
        expect(estIdentifieeParCin('  1184751K  ', '11041512')).toBe(false);
    });
});

describe('derniereDateNaissanceIso', () => {
    it('borne la veille, `before:today` du FormRequest refusant la date du jour', () => {
        expect(derniereDateNaissanceIso(new Date(2026, 7, 6))).toBe('2026-08-05');
    });

    it('recule correctement par-dessus un changement de mois et une année bissextile', () => {
        expect(derniereDateNaissanceIso(new Date(2026, 7, 1))).toBe('2026-07-31');
        expect(derniereDateNaissanceIso(new Date(2026, 0, 1))).toBe('2025-12-31');
        expect(derniereDateNaissanceIso(new Date(2024, 2, 1))).toBe('2024-02-29');
    });
});

/**
 * ⚠️ CETTE FONCTION NE NORMALISE PAS — ELLE REND TRANSPORTABLE.
 *
 * La normalisation TEJ (extraction du noyau, contrôle de forme `\d{7}[A-Z]`,
 * refus de ce qui n'a pas la forme observée) appartient au SERVEUR, et l'écran
 * ne la refait pas : il affiche la forme normalisée qu'on lui renvoie. Ici on
 * retire seulement ce qui ne peut pas tenir dans un SEGMENT D'URL, la route de
 * consultation portant l'identifiant dans son chemin.
 *
 * Le test qui compte est le dernier : une version cliente qui « aiderait » en
 * extrayant un noyau enverrait `1234567S` pour `1234567 SOCIETE ALPHA` — un
 * matricule parfaitement bien formé, appartenant à QUELQU'UN D'AUTRE, que le
 * serveur accepterait sans broncher. Il faut donc que la chaîne parte entière
 * et se fasse refuser (errors.md, piège de la normalisation ancrée d'un seul
 * côté).
 */
describe('identifiantTransportable', () => {
    it('laisse intacte une forme déjà propre', () => {
        expect(identifiantTransportable('1184751K')).toBe('1184751K');
        expect(identifiantTransportable('11041512')).toBe('11041512');
    });

    it('met en majuscules — la lettre-clé du référentiel est une capitale', () => {
        expect(identifiantTransportable('1631526v')).toBe('1631526V');
    });

    it('retire les séparateurs qui couperaient le segment d’URL en morceaux', () => {
        expect(identifiantTransportable('1631526/V/A/M/000')).toBe('1631526VAM000');
        expect(identifiantTransportable(' 1184751 K ')).toBe('1184751K');
    });

    it("N'EXTRAIT AUCUN NOYAU : une saisie polluée part entière, et le serveur la refuse", () => {
        // Si cette attente devient '1234567S', l'écran aura silencieusement
        // désigné un autre contribuable — et TEJ ne signalerait rien, son
        // écriture étant un upsert et sa validation purement cliente.
        expect(identifiantTransportable('1234567 SOCIETE ALPHA')).toBe('1234567SOCIETEALPHA');
    });

    it('ne laisse passer que [A-Z0-9], jamais un segment de remontée', () => {
        expect(identifiantTransportable('../../admin')).toBe('ADMIN');
        expect(identifiantTransportable('1184751K.json')).toBe('1184751KJSON');
    });
});
