import type { RuleRow, RuleTemplate } from '@/types/automatisations';
import { describe, expect, it } from 'vitest';
import {
    duplicateRuleForm,
    emptyRuleForm,
    NAME_MAX,
    RULE_ERRORS,
    ruleFormFromRow,
    ruleFormFromTemplate,
    rulePayload,
    validateRuleForm,
    type RuleFormData,
} from './rule-form';

const KNOWN = ['prenom', 'nom', 'logement', 'lien_checkin'];
const SOURCES = ['airbnb', 'booking', 'expedia', 'site', 'local', 'other'] as const;

function form(overrides: Partial<RuleFormData> = {}): RuleFormData {
    return { ...emptyRuleForm([...SOURCES]), name: 'Veille d’arrivée', bodies: { fr: 'Bonjour {prenom}', en: '' }, ...overrides };
}

describe('règle neuve', () => {
    it('commence « À valider », toutes provenances, tous les logements, la veille à 10:00', () => {
        const empty = emptyRuleForm([...SOURCES]);
        expect(empty.mode).toBe('approval');
        expect(empty.sources).toEqual([...SOURCES]);
        expect(empty.property_scope).toBe('all');
        expect(empty.trigger).toBe('before_checkin');
        expect(empty.offset_days).toBe('1');
        expect(empty.send_time).toBe('10:00');
    });

    it('une règle valide ne lève aucune erreur', () => {
        expect(validateRuleForm(form(), KNOWN)).toEqual({});
    });
});

describe('validateRuleForm — mêmes règles que le serveur', () => {
    it('exige un nom, sauf pour l’aperçu', () => {
        expect(validateRuleForm(form({ name: '  ' }), KNOWN)).toEqual({ name: RULE_ERRORS.nameRequired });
        expect(validateRuleForm(form({ name: '' }), KNOWN, true)).toEqual({});
    });

    it('exige au moins 1 jour pour les déclencheurs comptés en jours', () => {
        for (const trigger of ['before_checkin', 'during_stay', 'before_checkout', 'after_checkout'] as const) {
            expect(validateRuleForm(form({ trigger, offset_days: '0' }), KNOWN)).toEqual({ offset_days: RULE_ERRORS.offsetMin });
            expect(validateRuleForm(form({ trigger, offset_days: '' }), KNOWN)).toEqual({ offset_days: RULE_ERRORS.offsetMin });
        }
        expect(validateRuleForm(form({ offset_days: '61' }), KNOWN)).toEqual({ offset_days: RULE_ERRORS.offsetMax });
        // Le jour de l'arrivée ne lit pas le décalage.
        expect(validateRuleForm(form({ trigger: 'checkin_day', offset_days: '0' }), KNOWN)).toEqual({});
    });

    it('refuse une heure pendant les heures calmes, et dans l’heure qui les précède (passage horaire)', () => {
        for (const time of ['23:00', '07:59', '22:00', '21:59', '21:01']) {
            expect(validateRuleForm(form({ send_time: time }), KNOWN)).toEqual({ send_time: RULE_ERRORS.sendTimeQuiet });
        }
        for (const time of ['08:00', '21:00']) {
            expect(validateRuleForm(form({ send_time: time }), KNOWN)).toEqual({});
        }
        expect(validateRuleForm(form({ send_time: '9h' }), KNOWN)).toEqual({ send_time: RULE_ERRORS.sendTimeFormat });
    });

    it('« après la réservation » lit un délai de 0 à 1 440 minutes, pas une heure', () => {
        expect(validateRuleForm(form({ trigger: 'booking_confirmed', send_time: '23:30', delay_minutes: '30' }), KNOWN)).toEqual({});
        expect(validateRuleForm(form({ trigger: 'booking_confirmed', delay_minutes: '1441' }), KNOWN)).toEqual({
            delay_minutes: RULE_ERRORS.delayRange,
        });
    });

    it('exige une provenance, et des logements quand le périmètre est une sélection', () => {
        expect(validateRuleForm(form({ sources: [] }), KNOWN)).toEqual({ sources: RULE_ERRORS.sourcesRequired });
        expect(validateRuleForm(form({ property_scope: 'selected', property_ids: [] }), KNOWN)).toEqual({
            property_ids: RULE_ERRORS.propertiesRequired,
        });
        expect(validateRuleForm(form({ property_scope: 'selected', property_ids: [12] }), KNOWN)).toEqual({});
    });

    it('exige un texte dans au moins une langue', () => {
        expect(validateRuleForm(form({ bodies: { fr: '  ', en: '' } }), KNOWN)).toEqual({ bodies: RULE_ERRORS.bodyRequired });
        expect(validateRuleForm(form({ bodies: { fr: '', en: 'Hello {prenom}' } }), KNOWN)).toEqual({});
    });

    it('refuse une variable inconnue, dans le texte comme dans l’objet', () => {
        expect(validateRuleForm(form({ bodies: { fr: 'Bonjour {prénom}', en: '' } }), KNOWN)).toEqual({
            'bodies.fr': `${RULE_ERRORS.unknownToken}|{prénom}`,
        });
        expect(validateRuleForm(form({ email_subjects: { fr: '', en: 'Stay at {house}' } }), KNOWN)).toEqual({
            'email_subjects.en': `${RULE_ERRORS.unknownToken}|{house}`,
        });
    });
});

describe('rulePayload — seules les valeurs que le déclencheur lit partent', () => {
    it('une règle datée : décalage et heure, pas de délai', () => {
        expect(rulePayload(form({ offset_days: '2', delay_minutes: '45' }))).toMatchObject({ offset_days: 2, send_time: '10:00', delay_minutes: 0 });
    });

    it('le jour de l’arrivée : ni décalage ni délai', () => {
        expect(rulePayload(form({ trigger: 'checkin_day', offset_days: '3' }))).toMatchObject({
            offset_days: 0,
            send_time: '10:00',
            delay_minutes: 0,
        });
    });

    it('après la réservation : un délai, pas d’heure', () => {
        expect(rulePayload(form({ trigger: 'booking_confirmed', delay_minutes: '30' }))).toMatchObject({
            offset_days: 0,
            send_time: null,
            delay_minutes: 30,
        });
    });

    it('textes vides → null ; logements seulement pour une sélection', () => {
        const payload = rulePayload(form({ bodies: { fr: 'Bonjour', en: '   ' }, property_ids: [4, 5] }));
        expect(payload.bodies).toEqual({ fr: 'Bonjour', en: null });
        expect(payload.email_subjects).toEqual({ fr: null, en: null });
        expect(payload.property_ids).toEqual([]);
        expect(rulePayload(form({ property_scope: 'selected', property_ids: [4, 5] })).property_ids).toEqual([4, 5]);
    });
});

describe('ruleFormFromRow', () => {
    it('reprend une règle enregistrée sans rien perdre', () => {
        const row: RuleRow = {
            id: 3,
            name: 'Départ',
            isActive: true,
            mode: 'auto',
            trigger: 'checkout_day',
            offsetDays: 0,
            sendTime: '09:00',
            delayMinutes: 0,
            sources: ['airbnb'],
            propertyScope: 'selected',
            propertyIds: [7],
            bodies: { fr: 'Bon retour {prenom}', en: null },
            emailSubjects: { fr: null, en: 'Goodbye' },
            catchUp: false,
            activatedAt: '2026-09-01T00:00:00+01:00',
            stats: { sent30d: 0, pending: 0, failed30d: 0 },
        };

        expect(ruleFormFromRow(row)).toEqual({
            name: 'Départ',
            mode: 'auto',
            trigger: 'checkout_day',
            offset_days: '0',
            send_time: '09:00',
            delay_minutes: '0',
            sources: ['airbnb'],
            property_scope: 'selected',
            property_ids: [7],
            bodies: { fr: 'Bon retour {prenom}', en: '' },
            email_subjects: { fr: '', en: 'Goodbye' },
            catch_up: false,
        });
    });
});

const TEMPLATE_KNOWN = [...KNOWN, 'date_arrivee', 'date_depart', 'nb_nuits', 'nb_voyageurs'];

function template(overrides: Partial<RuleTemplate> = {}): RuleTemplate {
    return {
        key: 'checkin_online',
        group: 'landlord',
        replaces: null,
        name: 'Check-in en ligne',
        description: '3 jours avant l’arrivée : le lien du check-in en ligne.',
        trigger: 'before_checkin',
        offsetDays: 3,
        sendTime: '10:00',
        delayMinutes: 0,
        bodies: { fr: 'Bonjour {prenom},\n\nVotre check-in :\n{lien_checkin}', en: 'Hello {prenom},\n\nYour check-in:\n{lien_checkin}' },
        emailSubjects: { fr: 'Votre check-in en ligne — {logement}', en: 'Your online check-in — {logement}' },
        ...overrides,
    };
}

function savedRule(overrides: Partial<RuleRow> = {}): RuleRow {
    return {
        id: 9,
        name: 'Instructions',
        isActive: true,
        mode: 'auto',
        trigger: 'before_checkin',
        offsetDays: 2,
        sendTime: '18:30',
        delayMinutes: 0,
        sources: ['airbnb', 'site'],
        propertyScope: 'selected',
        propertyIds: [7, 11],
        bodies: { fr: 'Bonjour {prenom},\nÀ demain à {logement} !', en: 'Hello {prenom}' },
        emailSubjects: { fr: 'Demain — {logement}', en: null },
        catchUp: false,
        activatedAt: '2026-09-01T00:00:00+01:00',
        stats: { sent30d: 4, pending: 1, failed30d: 0 },
        ...overrides,
    };
}

describe('ruleFormFromTemplate — un modèle pré-remplit une NOUVELLE règle', () => {
    it('« À valider », toutes provenances, tous les logements, rattrapage', () => {
        const filled = ruleFormFromTemplate(template(), [...SOURCES]);

        expect(filled.mode).toBe('approval');
        expect(filled.sources).toEqual([...SOURCES]);
        expect(filled.property_scope).toBe('all');
        expect(filled.property_ids).toEqual([]);
        expect(filled.catch_up).toBe(true);
    });

    it('reprend le nom, le moment, les textes et les objets du modèle', () => {
        const source = template();
        const filled = ruleFormFromTemplate(source, [...SOURCES]);

        expect(filled.name).toBe('Check-in en ligne');
        expect(filled.trigger).toBe('before_checkin');
        expect(filled.offset_days).toBe('3');
        expect(filled.send_time).toBe('10:00');
        expect(filled.bodies).toEqual(source.bodies);
        expect(filled.email_subjects).toEqual(source.emailSubjects);
        // Les textes sont COPIÉS : modifier le formulaire ne touche pas le modèle.
        expect(filled.bodies).not.toBe(source.bodies);
    });

    it('« après la réservation » : un délai, et l’heure par défaut quand le modèle n’en a pas', () => {
        const filled = ruleFormFromTemplate(template({ trigger: 'booking_confirmed', offsetDays: 0, sendTime: null, delayMinutes: 30 }), [
            ...SOURCES,
        ]);

        expect(filled.send_time).toBe(emptyRuleForm([...SOURCES]).send_time);
        expect(filled.delay_minutes).toBe('30');
        expect(rulePayload(filled)).toMatchObject({ trigger: 'booking_confirmed', offset_days: 0, send_time: null, delay_minutes: 30 });
    });

    it('le jour de l’arrivée garde un décalage valable si l’on change de moment ensuite', () => {
        const filled = ruleFormFromTemplate(template({ trigger: 'checkin_day', offsetDays: 0, sendTime: '12:00' }), [...SOURCES]);

        expect(validateRuleForm({ ...filled, trigger: 'before_checkin' }, TEMPLATE_KNOWN)).toEqual({});
    });

    it('le formulaire pré-rempli passe la validation de l’écran', () => {
        expect(validateRuleForm(ruleFormFromTemplate(template(), [...SOURCES]), TEMPLATE_KNOWN)).toEqual({});
    });
});

describe('duplicateRuleForm — la copie d’une règle, à enregistrer comme une nouvelle', () => {
    it('préfixe le nom avec le libellé traduit', () => {
        expect(duplicateRuleForm(savedRule(), 'Copie de :name').name).toBe('Copie de Instructions');
        expect(duplicateRuleForm(savedRule(), 'Copy of :name').name).toBe('Copy of Instructions');
    });

    it('un nom déjà long reste dans la limite du serveur', () => {
        const copy = duplicateRuleForm(savedRule({ name: 'x'.repeat(NAME_MAX) }), 'Copie de :name');

        expect(copy.name).toHaveLength(NAME_MAX);
        expect(copy.name.startsWith('Copie de x')).toBe(true);
    });

    it('force « À valider », même depuis une règle automatique', () => {
        expect(duplicateRuleForm(savedRule({ mode: 'auto' }), 'Copie de :name').mode).toBe('approval');
    });

    it('reprend les logements, les provenances, le moment et le texte à l’identique', () => {
        const rule = savedRule();
        const copy = duplicateRuleForm(rule, 'Copie de :name');

        expect(copy.property_scope).toBe('selected');
        expect(copy.property_ids).toEqual([7, 11]);
        expect(copy.property_ids).not.toBe(rule.propertyIds);
        expect(copy.sources).toEqual(['airbnb', 'site']);
        expect(copy.trigger).toBe('before_checkin');
        expect(copy.offset_days).toBe('2');
        expect(copy.send_time).toBe('18:30');
        expect(copy.catch_up).toBe(false);
        expect(copy.bodies).toEqual({ fr: 'Bonjour {prenom},\nÀ demain à {logement} !', en: 'Hello {prenom}' });
        expect(copy.email_subjects).toEqual({ fr: 'Demain — {logement}', en: '' });
    });
});
