import { describe, expect, it } from 'vitest';
import { formatDateFr, formatDateTimeFr, formatDayHeader, formatRelative, localDayKey } from './relative-time';

/*
 * Toutes les dates sont construites dans le fuseau LOCAL de la machine qui
 * lance les tests (`new Date(a, m, j, h, min, s)`), puis passées en ISO UTC.
 * Les fonctions testées raisonnent elles aussi en local : le résultat ne
 * dépend donc pas du fuseau de la CI.
 */
const NOW = new Date(2026, 8, 23, 14, 0, 0); // mercredi 23/09/2026 14:00:00

/**
 * Intl sépare nombre et unité par des espaces insécables (U+00A0, U+202F) :
 * voulu à l'écran, neutralisé ici. NFKC les ramène tous deux à l'espace simple.
 */
function n(value: string): string {
    return value.normalize('NFKC');
}

function isoAgo(seconds: number): string {
    return new Date(NOW.getTime() - seconds * 1000).toISOString();
}

function isoLocal(y: number, m: number, d: number, h = 12, min = 0, s = 0): string {
    return new Date(y, m - 1, d, h, min, s).toISOString();
}

describe('formatRelative — secondes, minutes, heures', () => {
    it("dit « à l'instant » sous 45 secondes", () => {
        expect(n(formatRelative(isoAgo(0), 'fr', NOW))).toBe("à l'instant");
        expect(n(formatRelative(isoAgo(44), 'fr', NOW))).toBe("à l'instant");
        expect(n(formatRelative(isoAgo(10), 'en', NOW))).toBe('just now');
    });

    it("tolère une horloge serveur en avance de quelques secondes", () => {
        expect(n(formatRelative(isoAgo(-20), 'fr', NOW))).toBe("à l'instant");
    });

    it('passe à la minute dès 45 secondes, sans jamais afficher « 0 min »', () => {
        expect(n(formatRelative(isoAgo(45), 'fr', NOW))).toBe('il y a 1 min');
    });

    it('compte les minutes en les tronquant', () => {
        expect(n(formatRelative(isoAgo(4 * 60 + 50), 'fr', NOW))).toBe('il y a 4 min');
        expect(n(formatRelative(isoAgo(59 * 60 + 59), 'fr', NOW))).toBe('il y a 59 min');
    });

    it('compte les heures en les tronquant — 1 h 59 reste « 1 h »', () => {
        expect(n(formatRelative(isoAgo(60 * 60), 'fr', NOW))).toBe('il y a 1 h');
        expect(n(formatRelative(isoAgo(119 * 60), 'fr', NOW))).toBe('il y a 1 h');
        expect(n(formatRelative(isoAgo(23 * 3600 + 59 * 60), 'fr', NOW))).toBe('il y a 23 h');
    });

    it("parle anglais quand la locale est l'anglais", () => {
        expect(n(formatRelative(isoAgo(4 * 60), 'en', NOW))).toBe('4 min. ago');
        expect(n(formatRelative(isoAgo(2 * 3600), 'en', NOW))).toBe('2 hr. ago');
    });

    it('retombe sur le français pour une locale inconnue ou vide', () => {
        expect(n(formatRelative(isoAgo(4 * 60), 'de', NOW))).toBe('il y a 4 min');
        expect(n(formatRelative(isoAgo(4 * 60), '', NOW))).toBe('il y a 4 min');
    });
});

describe('formatRelative — jours calendaires', () => {
    it("dit « hier » pour la veille calendaire, au-delà de 24 h", () => {
        expect(n(formatRelative(isoLocal(2026, 9, 22, 9, 0), 'fr', NOW))).toBe('hier');
        expect(n(formatRelative(isoLocal(2026, 9, 22, 9, 0), 'en', NOW))).toBe('yesterday');
    });

    it("préfère l'heure au jour tant que moins de 24 h se sont écoulées", () => {
        // Hier à 15 h, lu aujourd'hui à 14 h : 23 h, c'est plus précis que « hier ».
        expect(n(formatRelative(isoLocal(2026, 9, 22, 15, 0), 'fr', NOW))).toBe('il y a 23 h');
    });

    it('compte en jours calendaires, pas en tranches de 24 h', () => {
        // Lundi 23 h → mercredi 14 h : 39 h, mais deux jours calendaires.
        expect(n(formatRelative(isoLocal(2026, 9, 21, 23, 0), 'fr', NOW))).toBe('avant-hier');
        expect(n(formatRelative(isoLocal(2026, 9, 20, 8, 0), 'fr', NOW))).toBe('il y a 3 jours');
        expect(n(formatRelative(isoLocal(2026, 9, 20, 8, 0), 'en', NOW))).toBe('3 days ago');
    });

    it('reste relatif jusqu’à six jours', () => {
        expect(n(formatRelative(isoLocal(2026, 9, 17, 8, 0), 'fr', NOW))).toBe('il y a 6 jours');
    });

    it('rend la date jj/mm/aaaa au-delà de six jours, dans les deux langues', () => {
        expect(n(formatRelative(isoLocal(2026, 9, 16, 8, 0), 'fr', NOW))).toBe('16/09/2026');
        expect(n(formatRelative(isoLocal(2026, 1, 5, 8, 0), 'en', NOW))).toBe('05/01/2026');
    });

    it('rend la chaîne telle quelle si elle ne se lit pas comme une date', () => {
        expect(n(formatRelative('pas une date', 'fr', NOW))).toBe('pas une date');
    });
});

describe('formatDateTimeFr / formatDateFr', () => {
    it('rend jj/mm/aaaa HH:mm:ss, zéros compris', () => {
        expect(formatDateTimeFr(isoLocal(2026, 3, 4, 5, 6, 7))).toBe('04/03/2026 05:06:07');
    });

    it('rend jj/mm/aaaa seul', () => {
        expect(formatDateFr(isoLocal(2026, 12, 31, 23, 59, 59))).toBe('31/12/2026');
    });

    it("lit une date ISO avec décalage horaire comme un instant, rendu à l'heure locale", () => {
        const instant = new Date('2026-09-23T14:02:11+01:00');
        const attendu = `${String(instant.getDate()).padStart(2, '0')}/09/2026 ${String(instant.getHours()).padStart(2, '0')}:02:11`;

        expect(formatDateTimeFr('2026-09-23T14:02:11+01:00')).toBe(attendu);
    });

    it('rend la chaîne telle quelle si elle ne se lit pas comme une date', () => {
        expect(formatDateTimeFr('n/a')).toBe('n/a');
    });
});

describe('formatDayHeader', () => {
    it("préfixe « Aujourd'hui » au jour courant", () => {
        expect(formatDayHeader(isoLocal(2026, 9, 23, 8, 0), 'fr', NOW)).toBe('Aujourd’hui · mercredi 23/09/2026');
    });

    it('préfixe « Hier » à la veille', () => {
        expect(formatDayHeader(isoLocal(2026, 9, 22, 23, 30), 'fr', NOW)).toBe('Hier · mardi 22/09/2026');
    });

    it('rend le jour de la semaine seul, capitalisé, au-delà', () => {
        expect(formatDayHeader(isoLocal(2026, 9, 21, 8, 0), 'fr', NOW)).toBe('Lundi 21/09/2026');
    });

    it("suit la langue pour les mots, mais garde jj/mm/aaaa", () => {
        expect(formatDayHeader(isoLocal(2026, 9, 23, 8, 0), 'en', NOW)).toBe('Today · Wednesday 23/09/2026');
        expect(formatDayHeader(isoLocal(2026, 9, 21, 8, 0), 'en', NOW)).toBe('Monday 21/09/2026');
    });
});

describe('localDayKey', () => {
    it('regroupe deux instants du même jour local', () => {
        expect(localDayKey(isoLocal(2026, 9, 23, 0, 1))).toBe('2026-09-23');
        expect(localDayKey(isoLocal(2026, 9, 23, 23, 59))).toBe('2026-09-23');
    });
});
