/**
 * useRecurrence.spec.js
 *
 * Testa as funções puras do módulo de recorrência:
 *   - generateDates  → geração de datas por tipo de regra
 *   - expandRules    → aplicação de exceções sobre as ocorrências
 *   - mergeWithStoredSessions → merge de ocorrências virtuais com eventos reais
 *
 * Não usa Supabase — sem mocks necessários.
 */

import { describe, it, expect } from 'vitest';
import { generateDates, expandRules, mergeWithStoredSessions } from '../useRecurrence.js';

// ─── helpers de fixture ───────────────────────────────────────────────────────

function d(iso) {
    const [y, m, day] = iso.split('-').map(Number);
    return new Date(y, m - 1, day);
}

function rule(overrides = {}) {
    return {
        id: 'rule-1',
        owner_id: 'owner-1',
        tenant_id: 'tenant-1',
        patient_id: 'patient-1',
        therapist_id: 'therapist-1',
        status: 'ativo',
        type: 'weekly',
        weekdays: [1], // segunda
        interval: 1,
        start_date: '2026-03-02', // segunda
        end_date: null,
        max_occurrences: null,
        open_ended: true,
        start_time: '09:00',
        end_time: '10:00',
        ...overrides
    };
}

function exception(overrides = {}) {
    return {
        id: 'exc-1',
        recurrence_id: 'rule-1',
        original_date: '2026-03-09',
        type: 'cancel_session',
        new_date: null,
        ...overrides
    };
}

// ─── generateDates ────────────────────────────────────────────────────────────

describe('generateDates — weekly', () => {
    it('gera ocorrências semanais dentro do range', () => {
        const r = rule({ type: 'weekly', weekdays: [1], start_date: '2026-03-02' });
        const dates = generateDates(r, d('2026-03-01'), d('2026-03-31'));
        const isos = dates.map((d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`);
        expect(isos).toEqual(['2026-03-02', '2026-03-09', '2026-03-16', '2026-03-23', '2026-03-30']);
    });

    it('não gera antes do start_date da regra', () => {
        const r = rule({ type: 'weekly', weekdays: [1], start_date: '2026-03-16' });
        const dates = generateDates(r, d('2026-03-01'), d('2026-03-31'));
        expect(dates.every((d) => d >= new Date(2026, 2, 16))).toBe(true);
    });

    it('não gera após o end_date da regra', () => {
        const r = rule({ type: 'weekly', weekdays: [1], start_date: '2026-03-02', end_date: '2026-03-16' });
        const dates = generateDates(r, d('2026-03-01'), d('2026-03-31'));
        expect(dates.length).toBe(3); // 02, 09, 16
    });

    it('respeita max_occurrences dentro do range', () => {
        const r = rule({ type: 'weekly', weekdays: [1], start_date: '2026-03-02', max_occurrences: 2 });
        const dates = generateDates(r, d('2026-03-01'), d('2026-03-31'));
        expect(dates.length).toBe(2);
    });

    it('respeita max_occurrences globalmente — range começa na 3ª semana', () => {
        // 4 ocorrências totais, range começa na semana 3 → só 2 dentro do range
        const r = rule({ type: 'weekly', weekdays: [1], start_date: '2026-03-02', max_occurrences: 4 });
        const dates = generateDates(r, d('2026-03-15'), d('2026-04-30'));
        // 16, 23, 30 → mas max=4 globalmente (2 antes do range + 2 no range)
        expect(dates.length).toBe(2); // 2026-03-16, 2026-03-23
    });
});

describe('generateDates — biweekly', () => {
    it('gera ocorrências a cada 2 semanas', () => {
        const r = rule({ type: 'biweekly', weekdays: [1], interval: 2, start_date: '2026-03-02' });
        const dates = generateDates(r, d('2026-03-01'), d('2026-04-30'));
        const isos = dates.map((d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`);
        expect(isos).toEqual(['2026-03-02', '2026-03-16', '2026-03-30', '2026-04-13', '2026-04-27']);
    });
});

describe('generateDates — custom_weekdays', () => {
    it('gera ocorrências em múltiplos dias da semana', () => {
        const r = rule({ type: 'custom_weekdays', weekdays: [1, 3], start_date: '2026-03-02' }); // seg e qua
        const dates = generateDates(r, d('2026-03-01'), d('2026-03-08'));
        const isos = dates.map((d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`);
        expect(isos).toEqual(['2026-03-02', '2026-03-04']);
    });

    it('respeita max_occurrences globalmente com custom_weekdays', () => {
        // 2 dias/semana, max=3 → semana 1 (02,04), semana 2 (09) e para
        const r = rule({ type: 'custom_weekdays', weekdays: [1, 3], start_date: '2026-03-02', max_occurrences: 3 });
        const dates = generateDates(r, d('2026-03-01'), d('2026-03-31'));
        expect(dates.length).toBe(3);
    });

    it('max_occurrences globalmente — range começa na semana 2', () => {
        // semana 1 já consumiu 2 ocorrências (02, 04), max=3 → só 1 no range (09)
        const r = rule({ type: 'custom_weekdays', weekdays: [1, 3], start_date: '2026-03-02', max_occurrences: 3 });
        const dates = generateDates(r, d('2026-03-08'), d('2026-03-31'));
        expect(dates.length).toBe(1);
    });
});

describe('generateDates — monthly', () => {
    it('gera ocorrências mensais no mesmo dia', () => {
        const r = rule({ type: 'monthly', weekdays: [1], start_date: '2026-01-15' });
        const dates = generateDates(r, d('2026-01-01'), d('2026-04-30'));
        expect(dates.length).toBe(4);
        expect(dates.every((d) => d.getDate() === 15)).toBe(true);
    });

    it('respeita max_occurrences globalmente — range começa no mês 3', () => {
        const r = rule({ type: 'monthly', weekdays: [1], start_date: '2026-01-15', max_occurrences: 3 });
        const dates = generateDates(r, d('2026-03-01'), d('2026-12-31'));
        expect(dates.length).toBe(1); // só março (jan+fev já consumiram 2 de 3)
    });
});

describe('generateDates — yearly', () => {
    it('gera ocorrências anuais', () => {
        const r = rule({ type: 'yearly', weekdays: [1], start_date: '2024-06-15' });
        const dates = generateDates(r, d('2024-01-01'), d('2027-12-31'));
        expect(dates.length).toBe(4); // 2024, 2025, 2026, 2027
    });

    it('respeita max_occurrences globalmente — range começa no ano 3', () => {
        const r = rule({ type: 'yearly', weekdays: [1], start_date: '2024-06-15', max_occurrences: 3 });
        const dates = generateDates(r, d('2026-01-01'), d('2030-12-31'));
        expect(dates.length).toBe(1); // só 2026 (2024+2025 já consumiram 2 de 3)
    });
});

// ─── expandRules ─────────────────────────────────────────────────────────────

describe('expandRules', () => {
    it('gera ocorrência normal sem exceção', () => {
        const rules = [rule()];
        const occs = expandRules(rules, [], d('2026-03-01'), d('2026-03-08'));
        expect(occs.length).toBe(1);
        expect(occs[0].status).toBe('agendado');
        expect(occs[0].exception_type).toBeNull();
    });

    it('cancela ocorrência com cancel_session', () => {
        const rules = [rule()];
        const excs = [exception({ type: 'cancel_session', original_date: '2026-03-02' })];
        const occs = expandRules(rules, excs, d('2026-03-01'), d('2026-03-08'));
        expect(occs[0].status).toBe('cancelado');
        expect(occs[0].exception_type).toBe('cancel_session');
    });

    it('marca falta com patient_missed', () => {
        const rules = [rule()];
        const excs = [exception({ type: 'patient_missed', original_date: '2026-03-02' })];
        const occs = expandRules(rules, excs, d('2026-03-01'), d('2026-03-08'));
        expect(occs[0].status).toBe('faltou');
    });

    it('remarca ocorrência para nova data', () => {
        const rules = [rule()];
        const excs = [
            exception({
                type: 'reschedule_session',
                original_date: '2026-03-02',
                new_date: '2026-03-04'
            })
        ];
        const occs = expandRules(rules, excs, d('2026-03-01'), d('2026-03-08'));
        // A ocorrência do dia 02 foi movida para 04
        expect(occs[0].status).toBe('remarcado');
        expect(occs[0].exception_type).toBe('reschedule_session');
        // inicio_em reflete a nova data (04); original_date no main loop recebe new_date
        expect(occs[0].inicio_em).toContain('2026-03-04');
    });

    it('post-pass: remarcação inbound — original fora do range, new_date dentro', () => {
        // Regra começa em 02/03 (segunda). original_date = 09/03 está FORA do range 16-22.
        // new_date = 17/03 está DENTRO do range.
        const rules = [rule({ start_date: '2026-03-02' })];
        const excs = [
            exception({
                type: 'reschedule_session',
                original_date: '2026-03-09', // fora do range
                new_date: '2026-03-17' // dentro do range
            })
        ];
        const occs = expandRules(rules, excs, d('2026-03-16'), d('2026-03-22'));
        const remarcado = occs.find((o) => o.status === 'remarcado');
        expect(remarcado).toBeDefined();
        expect(remarcado.original_date).toBe('2026-03-09');
        expect(remarcado.inicio_em).toContain('2026-03-17');
    });

    it('ignora regra cancelada', () => {
        const rules = [rule({ status: 'cancelado' })];
        const occs = expandRules(rules, [], d('2026-03-01'), d('2026-03-31'));
        expect(occs.length).toBe(0);
    });
});

// ─── mergeWithStoredSessions ──────────────────────────────────────────────────

describe('mergeWithStoredSessions', () => {
    it('sessão real substitui ocorrência virtual para a mesma regra+data', () => {
        const occs = [
            {
                recurrence_id: 'rule-1',
                original_date: '2026-03-02',
                status: 'agendado',
                is_occurrence: true,
                is_real_session: false,
                titulo: 'Virtual'
            }
        ];
        const storedRows = [
            {
                id: 'ev-real-1',
                recurrence_id: 'rule-1',
                recurrence_date: '2026-03-02',
                status: 'realizado',
                titulo: 'Real'
            }
        ];
        const merged = mergeWithStoredSessions(occs, storedRows);
        expect(merged.length).toBe(1);
        expect(merged[0].is_real_session).toBe(true);
        expect(merged[0].status).toBe('realizado');
        expect(merged[0].titulo).toBe('Real');
    });

    it('mantém ocorrência virtual quando não há sessão real', () => {
        const occs = [
            {
                recurrence_id: 'rule-1',
                original_date: '2026-03-02',
                status: 'agendado',
                is_occurrence: true
            }
        ];
        const merged = mergeWithStoredSessions(occs, []);
        expect(merged.length).toBe(1);
        expect(merged[0].is_occurrence).toBe(true);
    });

    it('adiciona sessão real órfã (sem ocorrência correspondente)', () => {
        const storedRows = [
            {
                id: 'ev-orphan',
                recurrence_id: 'rule-1',
                recurrence_date: '2026-03-30', // data fora do range expandido
                status: 'agendado'
            }
        ];
        const merged = mergeWithStoredSessions([], storedRows);
        expect(merged.length).toBe(1);
        expect(merged[0].is_real_session).toBe(true);
    });

    it('não duplica quando há tanto ocorrência quanto sessão real', () => {
        const occs = [
            { recurrence_id: 'rule-1', original_date: '2026-03-02', is_occurrence: true },
            { recurrence_id: 'rule-1', original_date: '2026-03-09', is_occurrence: true }
        ];
        const stored = [{ recurrence_id: 'rule-1', recurrence_date: '2026-03-02', status: 'realizado' }];
        const merged = mergeWithStoredSessions(occs, stored);
        expect(merged.length).toBe(2);
    });
});
