/**
 * agendaEventHelpers.spec.js — A66 sub-sessão 1A
 *
 * Cobertura dos helpers PUROS extraídos do AgendaEventDialog. Cada função
 * é determinística (sem refs reativos, sem I/O) — bateria foca em:
 *   - happy path (entrada típica)
 *   - edge cases (null/undefined/'')
 *   - boundaries (0min, 24h wrapping, descontos > subtotal)
 *   - matriz de inputs nos mappers de status
 */
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
    patientInitials,
    fmtBRL,
    fmtJornadaHora,
    fmtDateBR,
    fmtDateBRLong,
    fmtTime,
    fmtDuracao,
    fmtSerieHora,
    nomeDiaSemana,
    fmtWeekdayShort,
    fmtDayNum,
    fmtMonthShort,
    hhmmToMin,
    minToHHMM,
    isoToHHMM,
    isPast,
    isNativeSession,
    isForaDoPlano,
    addMinutesDate,
    calcMinutes,
    calcFinalPrice,
    labelStatusSessao,
    statusSeverity,
    statusExtraClass
} from '../agendaEventHelpers';

describe('patientInitials', () => {
    it('extrai 2 iniciais de nome composto', () => {
        expect(patientInitials('Ana Souza Ferreira')).toBe('AF');
        expect(patientInitials('joão silva')).toBe('JS');
    });
    it('faz slice 2 do único nome quando não há sobrenome', () => {
        expect(patientInitials('Maria')).toBe('MA');
    });
    it('retorna ? quando vazio/null', () => {
        expect(patientInitials('')).toBe('?');
        expect(patientInitials(null)).toBe('?');
        expect(patientInitials(undefined)).toBe('?');
        expect(patientInitials('   ')).toBe('?');
    });
    it('lida com múltiplos espaços', () => {
        expect(patientInitials('  ana   maria  ')).toBe('AM');
    });
});

describe('fmtBRL', () => {
    it('formata número como BRL', () => {
        expect(fmtBRL(1234.56)).toMatch(/R\$\s?1\.234,56/);
        expect(fmtBRL(0)).toMatch(/R\$\s?0,00/);
    });
    it('null/undefined → "—"', () => {
        expect(fmtBRL(null)).toBe('—');
        expect(fmtBRL(undefined)).toBe('—');
    });
    it('0 NÃO retorna "—" (é valor válido)', () => {
        expect(fmtBRL(0)).not.toBe('—');
    });
});

describe('fmtJornadaHora', () => {
    it('hora cheia sem minutos', () => {
        expect(fmtJornadaHora('09:00')).toBe('9h');
        expect(fmtJornadaHora('14:00')).toBe('14h');
    });
    it('hora com minutos formata "Xh{MM}"', () => {
        expect(fmtJornadaHora('14:30')).toBe('14h30');
        expect(fmtJornadaHora('09:05')).toBe('9h05');
    });
    it('lida com null/string vazia (default 00:00)', () => {
        expect(fmtJornadaHora(null)).toBe('0h');
        expect(fmtJornadaHora('')).toBe('0h');
    });
    it('aceita "HH:MM:SS" truncando segundos', () => {
        expect(fmtJornadaHora('14:30:00')).toBe('14h30');
    });
});

describe('fmtDuracao', () => {
    it('só horas', () => {
        expect(fmtDuracao(60)).toBe('1h');
        expect(fmtDuracao(120)).toBe('2h');
    });
    it('horas + minutos', () => {
        expect(fmtDuracao(90)).toBe('1h 30min');
        expect(fmtDuracao(135)).toBe('2h 15min');
    });
    it('só minutos', () => {
        expect(fmtDuracao(45)).toBe('45min');
        expect(fmtDuracao(15)).toBe('15min');
    });
    it('0/null/undefined → "—"', () => {
        expect(fmtDuracao(0)).toBe('—');
        expect(fmtDuracao(null)).toBe('—');
        expect(fmtDuracao(undefined)).toBe('—');
    });
});

describe('fmtSerieHora', () => {
    it('trunca segundos da string TIME', () => {
        expect(fmtSerieHora('14:30:00')).toBe('14:30');
        expect(fmtSerieHora('09:00:45')).toBe('09:00');
    });
    it('mantém HH:MM se sem segundos', () => {
        expect(fmtSerieHora('14:30')).toBe('14:30');
    });
    it('null/undefined → "—"', () => {
        expect(fmtSerieHora(null)).toBe('—');
        expect(fmtSerieHora('')).toBe('—');
    });
});

describe('nomeDiaSemana', () => {
    it('mapeia 0-6 corretamente', () => {
        expect(nomeDiaSemana(0)).toBe('domingo');
        expect(nomeDiaSemana(1)).toBe('segunda');
        expect(nomeDiaSemana(6)).toBe('sábado');
    });
    it('aceita string numérica', () => {
        expect(nomeDiaSemana('3')).toBe('quarta');
    });
    it('null/undefined → "domingo" (fallback 0)', () => {
        expect(nomeDiaSemana(null)).toBe('domingo');
        expect(nomeDiaSemana(undefined)).toBe('domingo');
    });
});

describe('fmtTime', () => {
    it('formata HH:MM de Date', () => {
        const d = new Date('2026-05-15T14:30:00');
        expect(fmtTime(d)).toMatch(/14:30/);
    });
    it('formata HH:MM de string ISO', () => {
        expect(fmtTime('2026-05-15T09:05:00')).toMatch(/09:05/);
    });
    it('null/undefined → "—"', () => {
        expect(fmtTime(null)).toBe('—');
        expect(fmtTime(undefined)).toBe('—');
    });
});

describe('hhmmToMin', () => {
    it('converte HH:MM em minutos do dia', () => {
        expect(hhmmToMin('00:00')).toBe(0);
        expect(hhmmToMin('01:30')).toBe(90);
        expect(hhmmToMin('14:00')).toBe(840);
        expect(hhmmToMin('23:59')).toBe(1439);
    });
    it('aceita HH:MM:SS truncando', () => {
        expect(hhmmToMin('14:30:00')).toBe(870);
    });
    it('null/string vazia → 0', () => {
        expect(hhmmToMin(null)).toBe(0);
        expect(hhmmToMin('')).toBe(0);
    });
});

describe('minToHHMM', () => {
    it('converte minutos em HH:MM zero-padded', () => {
        expect(minToHHMM(0)).toBe('00:00');
        expect(minToHHMM(90)).toBe('01:30');
        expect(minToHHMM(840)).toBe('14:00');
    });
    it('faz wrapping em 24h', () => {
        expect(minToHHMM(1440)).toBe('00:00');
        expect(minToHHMM(1500)).toBe('01:00');
    });
    // Round-trip pra cobrir simetria das duas funções
    it('round-trip hhmmToMin → minToHHMM preserva valor', () => {
        const inputs = ['00:00', '08:15', '14:30', '23:59'];
        for (const h of inputs) {
            expect(minToHHMM(hhmmToMin(h))).toBe(h);
        }
    });
});

describe('isoToHHMM', () => {
    it('lê dígitos diretos de ISO sem timezone', () => {
        expect(isoToHHMM('2026-05-15T14:30:00')).toBe('14:30');
        expect(isoToHHMM('2026-05-15T09:05:30')).toBe('09:05');
    });
    it('null/undefined → null', () => {
        expect(isoToHHMM(null)).toBe(null);
        expect(isoToHHMM('')).toBe(null);
    });
    // ISO com Z/offset depende do timezone do sistema executando o teste,
    // então só verificamos que volta uma string HH:MM válida.
    it('ISO com Z retorna formato HH:MM', () => {
        const result = isoToHHMM('2026-05-15T14:30:00Z');
        expect(result).toMatch(/^\d{2}:\d{2}$/);
    });
});

describe('isPast', () => {
    let mockNow;
    beforeEach(() => {
        mockNow = new Date('2026-05-15T12:00:00').getTime();
        vi.useFakeTimers();
        vi.setSystemTime(mockNow);
    });
    afterEach(() => vi.useRealTimers());

    it('passado → true', () => {
        expect(isPast('2026-05-14T12:00:00')).toBe(true);
        expect(isPast('2025-01-01')).toBe(true);
    });
    it('futuro → false', () => {
        expect(isPast('2026-05-16T12:00:00')).toBe(false);
        expect(isPast('2030-01-01')).toBe(false);
    });
    it('null/undefined → false', () => {
        expect(isPast(null)).toBe(false);
        expect(isPast(undefined)).toBe(false);
        expect(isPast('')).toBe(false);
    });
});

describe('isNativeSession', () => {
    it('native_key="session" → true (qualquer case)', () => {
        expect(isNativeSession({ native_key: 'session' })).toBe(true);
        expect(isNativeSession({ native_key: 'SESSION' })).toBe(true);
        expect(isNativeSession({ native_key: 'Session' })).toBe(true);
    });
    it('outro native_key → false', () => {
        expect(isNativeSession({ native_key: 'meeting' })).toBe(false);
        expect(isNativeSession({ native_key: '' })).toBe(false);
        expect(isNativeSession({})).toBe(false);
    });
    it('null/undefined → false', () => {
        expect(isNativeSession(null)).toBe(false);
        expect(isNativeSession(undefined)).toBe(false);
    });
});

describe('isForaDoPlano', () => {
    it('limite null → tudo dentro do plano', () => {
        expect(isForaDoPlano('2030-01-01', null)).toBe(false);
        expect(isForaDoPlano('2030-01-01', undefined)).toBe(false);
    });
    it('data > limite → fora', () => {
        expect(isForaDoPlano('2026-12-31', '2026-06-30')).toBe(true);
    });
    it('data < limite → dentro', () => {
        expect(isForaDoPlano('2026-01-15', '2026-06-30')).toBe(false);
    });
    it('data == limite → dentro (operador é > e não >=)', () => {
        expect(isForaDoPlano('2026-06-30', '2026-06-30')).toBe(false);
    });
});

describe('addMinutesDate', () => {
    it('soma minutos retornando NOVA data', () => {
        const base = new Date('2026-05-15T10:00:00');
        const result = addMinutesDate(base, 90);
        expect(result.getHours()).toBe(11);
        expect(result.getMinutes()).toBe(30);
        // Não muta o original
        expect(base.getHours()).toBe(10);
    });
    it('subtrai com negativos', () => {
        const base = new Date('2026-05-15T10:00:00');
        const result = addMinutesDate(base, -30);
        expect(result.getHours()).toBe(9);
        expect(result.getMinutes()).toBe(30);
    });
    it('aceita string ISO', () => {
        const result = addMinutesDate('2026-05-15T10:00:00', 30);
        expect(result.getMinutes()).toBe(30);
    });
    it('null minutos → mesma data (0)', () => {
        const base = new Date('2026-05-15T10:00:00');
        const result = addMinutesDate(base, null);
        expect(result.getTime()).toBe(base.getTime());
    });
});

describe('calcMinutes', () => {
    it('diff positivo em minutos', () => {
        expect(calcMinutes('2026-05-15T10:00:00', '2026-05-15T11:30:00')).toBe(90);
        expect(calcMinutes('2026-05-15T10:00:00', '2026-05-15T10:50:00')).toBe(50);
    });
    it('diff negativo (b < a) → 0 (clamp)', () => {
        expect(calcMinutes('2026-05-15T11:00:00', '2026-05-15T10:00:00')).toBe(0);
    });
    it('null/undefined → null', () => {
        expect(calcMinutes(null, '2026-05-15T10:00:00')).toBe(null);
        expect(calcMinutes('2026-05-15T10:00:00', null)).toBe(null);
        expect(calcMinutes(null, null)).toBe(null);
    });
    it('mesma data → 0', () => {
        expect(calcMinutes('2026-05-15T10:00:00', '2026-05-15T10:00:00')).toBe(0);
    });
});

describe('calcFinalPrice', () => {
    it('sem descontos = subtotal', () => {
        expect(calcFinalPrice(100, 1, 0, 0)).toBe(100);
        expect(calcFinalPrice(50, 3, 0, 0)).toBe(150);
        expect(calcFinalPrice(100, 1, null, null)).toBe(100);
    });
    it('aplica desconto percentual', () => {
        expect(calcFinalPrice(100, 1, 10, 0)).toBe(90);
        expect(calcFinalPrice(200, 2, 25, 0)).toBe(300); // 400 - 25%
    });
    it('aplica desconto flat', () => {
        expect(calcFinalPrice(100, 1, 0, 20)).toBe(80);
    });
    it('combina percentual + flat', () => {
        // 100 - 10% - 5 = 100 - 10 - 5 = 85
        expect(calcFinalPrice(100, 1, 10, 5)).toBe(85);
    });
    it('descontos > subtotal → 0 (não negativo)', () => {
        expect(calcFinalPrice(100, 1, 0, 200)).toBe(0);
        expect(calcFinalPrice(100, 1, 110, 0)).toBe(0);
    });
});

describe('labelStatusSessao', () => {
    const cases = [
        ['agendado', 'Agendado'],
        ['realizado', 'Realizado'],
        ['faltou', 'Faltou'],
        ['cancelado', 'Cancelado'],
        ['remarcado', 'Remarcado']
    ];
    it.each(cases)('"%s" → "%s"', (status, expected) => {
        expect(labelStatusSessao(status)).toBe(expected);
    });
    it('desconhecido → "—"', () => {
        expect(labelStatusSessao('blablabla')).toBe('—');
        expect(labelStatusSessao(null)).toBe('—');
        expect(labelStatusSessao('')).toBe('—');
    });
});

describe('statusSeverity', () => {
    const cases = [
        ['agendado', 'info'],
        ['realizado', 'success'],
        ['faltou', 'warn'],
        ['cancelado', 'danger'],
        ['remarcado', 'secondary']
    ];
    it.each(cases)('"%s" → "%s"', (status, expected) => {
        expect(statusSeverity(status)).toBe(expected);
    });
    it('desconhecido → "secondary" (fallback)', () => {
        expect(statusSeverity('blablabla')).toBe('secondary');
        expect(statusSeverity(null)).toBe('secondary');
    });
});

describe('statusExtraClass', () => {
    it('remarcado → "tag-remarcado"', () => {
        expect(statusExtraClass('remarcado')).toBe('tag-remarcado');
    });
    it('outros → ""', () => {
        expect(statusExtraClass('agendado')).toBe('');
        expect(statusExtraClass('realizado')).toBe('');
        expect(statusExtraClass(null)).toBe('');
    });
});

describe('fmt date helpers (Date input)', () => {
    const d = new Date('2026-05-15T14:30:00');

    it('fmtDateBR retorna formato dd-mes-aaaa pt-BR', () => {
        const result = fmtDateBR(d);
        expect(result).toMatch(/15.*mai.*2026/i);
    });
    it('fmtDateBRLong inclui weekday', () => {
        const result = fmtDateBRLong(d);
        expect(result).toMatch(/15.*mai/i);
        // Pelo menos contém algum dia da semana
        expect(result).toMatch(/dom|seg|ter|qua|qui|sex|s[áa]b/i);
    });
    it('fmtWeekdayShort retorna 3 chars', () => {
        const r = fmtWeekdayShort('2026-05-15T14:30:00');
        expect(r.length).toBeLessThanOrEqual(3);
    });
    it('fmtDayNum extrai dia do mês', () => {
        expect(fmtDayNum('2026-05-15T14:30:00')).toBe(15);
    });
    it('fmtMonthShort sem ponto final', () => {
        const r = fmtMonthShort('2026-05-15T14:30:00');
        expect(r).not.toContain('.');
    });
});
