/**
 * useAgendaEventActions.spec.js — A66 sub-sessão 1C-i
 *
 * Foco: handlers de save/delete e helpers puros de payload.
 * Os watchers (status confirm, billingType, samePatientConflict) são
 * testados indiretamente via setup do composable + mutações no form.
 *
 * Mock estratégia:
 *  - useToast/useConfirm: capturamos as chamadas em arrays/callbacks
 *  - supabase: mock o builder chain pra retornar { data, error }
 *  - composer: criamos manualmente um objeto com refs computeds que
 *    espelham o contrato esperado (não rodamos o composer real pra
 *    isolar o teste só nas actions)
 */
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ref, computed } from 'vue';

// ── Mocks de dependências ───────────────────────────────────────────
const toastAdd = vi.fn();
vi.mock('primevue/usetoast', () => ({
    useToast: () => ({ add: toastAdd })
}));

let _confirmAccept = null;
let _confirmReject = null;
let _confirmCalls = [];
const confirmRequire = vi.fn((opts) => {
    _confirmCalls.push(opts);
    _confirmAccept = opts.accept;
    _confirmReject = opts.reject;
});
vi.mock('primevue/useconfirm', () => ({
    useConfirm: () => ({ require: confirmRequire })
}));

const supabaseUpdateMock = vi.fn();
const supabaseSelectMock = vi.fn();
vi.mock('@/lib/supabase/client', () => ({
    supabase: {
        from: vi.fn(() => ({
            update: (...args) => {
                supabaseUpdateMock(...args);
                return {
                    eq: () => ({
                        select: () => ({
                            single: () => Promise.resolve({ data: { id: 'evt-1', status: 'cancelado' }, error: null })
                        })
                    })
                };
            },
            select: (...args) => {
                supabaseSelectMock(...args);
                return {
                    eq: () => ({
                        gte: () => ({
                            lt: () => ({
                                limit: () => ({
                                    neq: () => ({ maybeSingle: () => Promise.resolve({ data: null }) }),
                                    maybeSingle: () => Promise.resolve({ data: null })
                                })
                            })
                        })
                    })
                };
            }
        }))
    }
}));

const { useAgendaEventActions } = await import('../useAgendaEventActions.js');

// ── Helper: composer fake com o contrato mínimo ─────────────────────
function makeComposer(overrides = {}) {
    const form = ref({
        id: null,
        owner_id: 'owner-1',
        terapeuta_id: null,
        paciente_id: null,
        paciente_nome: '',
        paciente_status: '',
        commitment_id: 'c-1',
        titulo_custom: '',
        status: 'agendado',
        observacoes: '',
        dia: new Date('2026-05-15T00:00:00'),
        startTime: '14:00',
        duracaoMin: 50,
        modalidade: 'presencial',
        extra_fields: {},
        price: null,
        insurance_plan_id: null,
        insurance_guide_number: null,
        insurance_value: null,
        insurance_plan_service_id: null,
        ...(overrides.formExtra || {})
    });

    const inicioDateTime = computed(() => {
        if (!form.value.dia || !form.value.startTime) return null;
        const d = new Date(form.value.dia);
        const [h, m] = form.value.startTime.split(':').map(Number);
        d.setHours(h, m, 0, 0);
        return d;
    });
    const fimDateTime = computed(() => {
        if (!inicioDateTime.value) return null;
        const d = new Date(inicioDateTime.value);
        d.setMinutes(d.getMinutes() + (form.value.duracaoMin || 50));
        return d;
    });

    return {
        form,
        canSave: ref(true),
        timeConflict: ref(null),
        isEdit: ref(false),
        isSessionEvent: ref(true),
        requiresPatient: ref(false),
        hasSerie: ref(false),
        billingType: ref('particular'),
        recorrenciaType: ref('avulsa'),
        diaSemanaRecorrencia: ref(0),
        diasSelecionados: ref([]),
        dataFimCalculada: ref(null),
        qtdSessoesEfetiva: ref(4),
        ocorrenciasComConflito: ref([]),
        editScope: ref('somente_este'),
        editScopeOptions: ref([
            { value: 'somente_este', label: 'Somente esta' },
            { value: 'todos', label: 'Todas' }
        ]),
        visible: ref(true),
        inicioDateTime,
        fimDateTime,
        computedTitulo: ref('Sessão'),
        ...overrides
    };
}

function setup(composerOverrides = {}, propsOverrides = {}) {
    toastAdd.mockClear();
    confirmRequire.mockClear();
    _confirmCalls = [];
    _confirmAccept = null;
    _confirmReject = null;

    const composer = makeComposer(composerOverrides);
    const commitmentItems = ref([]);
    const servicePickerSel = ref(null);
    const selectedPlanService = ref(null);
    const saveCommitmentItems = vi.fn().mockResolvedValue();
    const props = { eventRow: null, ...propsOverrides };
    const emitted = [];
    const emit = (...args) => emitted.push(args);

    const actions = useAgendaEventActions({
        composer,
        commitmentItems,
        servicePickerSel,
        selectedPlanService,
        saveCommitmentItems,
        props,
        emit
    });

    return { composer, commitmentItems, servicePickerSel, selectedPlanService, props, emit, emitted, actions, saveCommitmentItems };
}

// ════════════════════════════════════════════════════════════════════
describe('buildSavePayload (helper puro)', () => {
    it('monta payload com session', () => {
        const { actions } = setup();
        const payload = actions.buildSavePayload({
            form: {
                owner_id: 'o-1',
                terapeuta_id: 't-1',
                paciente_id: 'p-1',
                status: 'agendado',
                modalidade: 'presencial',
                observacoes: 'note',
                commitment_id: 'c-1',
                titulo_custom: '',
                extra_fields: {},
                price: 100,
                insurance_plan_id: null,
                insurance_guide_number: null,
                insurance_value: null,
                insurance_plan_service_id: null
            },
            requiresPatient: true,
            isSessionEvent: true,
            computedTitulo: 'Ana [Sessão]',
            inicioISO: '2026-05-15T14:00:00.000Z',
            fimISO: '2026-05-15T14:50:00.000Z'
        });
        expect(payload).toMatchObject({
            owner_id: 'o-1',
            paciente_id: 'p-1',
            patient_id: 'p-1',
            tipo: 'sessao',
            status: 'agendado',
            titulo: 'Ana [Sessão]',
            inicio_em: '2026-05-15T14:00:00.000Z',
            determined_commitment_id: 'c-1',
            price: 100
        });
    });

    it('paciente_id fica null quando NÃO é session', () => {
        const { actions } = setup();
        const payload = actions.buildSavePayload({
            form: { paciente_id: 'p-1', commitment_id: 'c-meeting' },
            requiresPatient: false,
            isSessionEvent: false,
            computedTitulo: 'Reunião',
            inicioISO: 'X',
            fimISO: 'Y'
        });
        expect(payload.paciente_id).toBe(null);
        expect(payload.patient_id).toBe(null);
        expect(payload.price).toBe(null); // só session salva price
    });

    it('extra_fields=null quando objeto vazio', () => {
        const { actions } = setup();
        const payload = actions.buildSavePayload({
            form: { extra_fields: {} },
            requiresPatient: false,
            isSessionEvent: false,
            computedTitulo: '',
            inicioISO: 'X',
            fimISO: 'Y'
        });
        expect(payload.extra_fields).toBe(null);
    });

    it('extra_fields preservado quando tem chaves', () => {
        const { actions } = setup();
        const payload = actions.buildSavePayload({
            form: { extra_fields: { custom: 'v' } },
            requiresPatient: false,
            isSessionEvent: false,
            computedTitulo: '',
            inicioISO: 'X',
            fimISO: 'Y'
        });
        expect(payload.extra_fields).toEqual({ custom: 'v' });
    });
});

describe('buildRecorrenciaPayload (helper puro)', () => {
    it('avulsa → null', () => {
        const { actions } = setup();
        expect(
            actions.buildRecorrenciaPayload({
                recorrenciaType: 'avulsa',
                diaSemanaRecorrencia: 0,
                diasSelecionados: [],
                startTime: '14:00',
                duracaoMin: 50,
                dataFimCalculada: null,
                qtdSessoesEfetiva: 0,
                serieValorMode: 'multiplicar',
                commitmentItemsList: [],
                ocorrenciasComConflito: []
            })
        ).toBe(null);
    });

    it('semanal monta payload completo', () => {
        const { actions } = setup();
        const dataFim = new Date('2026-06-05T00:00:00.000Z');
        const result = actions.buildRecorrenciaPayload({
            recorrenciaType: 'semanal',
            diaSemanaRecorrencia: 5,
            diasSelecionados: [],
            startTime: '14:00',
            duracaoMin: 50,
            dataFimCalculada: dataFim,
            qtdSessoesEfetiva: 4,
            serieValorMode: 'multiplicar',
            commitmentItemsList: [{ id: 'i-1' }],
            ocorrenciasComConflito: []
        });
        expect(result).toMatchObject({
            tipo: 'recorrente',
            tipoFreq: 'semanal',
            diaSemana: 5,
            horaInicio: '14:00:00',
            duracaoMin: 50,
            qtdSessoes: 4,
            serieValorMode: 'multiplicar',
            commitmentItems: [{ id: 'i-1' }]
        });
        expect(result.dataFim).toBe(dataFim.toISOString());
    });

    it('inclui só ocorrências COM conflict no array conflitos', () => {
        const { actions } = setup();
        const result = actions.buildRecorrenciaPayload({
            recorrenciaType: 'semanal',
            diaSemanaRecorrencia: 5,
            diasSelecionados: [],
            startTime: '14:00',
            duracaoMin: 50,
            dataFimCalculada: new Date(),
            qtdSessoesEfetiva: 4,
            serieValorMode: 'multiplicar',
            commitmentItemsList: [],
            ocorrenciasComConflito: [
                { date: new Date('2026-05-15'), conflict: { type: 'feriado', label: 'X' } },
                { date: new Date('2026-05-22'), conflict: null },
                { date: new Date('2026-05-29'), conflict: { type: 'pausa', label: 'Y' } }
            ]
        });
        expect(result.conflitos).toHaveLength(2);
        expect(result.conflitos[0].conflict.type).toBe('feriado');
        expect(result.conflitos[1].conflict.type).toBe('pausa');
    });
});

describe('onSave', () => {
    it('aborta se !canSave (não emite)', () => {
        const { actions, emitted } = setup({ canSave: ref(false) });
        actions.onSave();
        expect(emitted).toHaveLength(0);
    });

    it('aborta com toast se timeConflict presente', () => {
        const { actions, emitted } = setup({ timeConflict: ref('Conflito com Maria às 14:30') });
        actions.onSave();
        expect(emitted).toHaveLength(0);
        expect(toastAdd).toHaveBeenCalledWith(
            expect.objectContaining({
                severity: 'warn',
                summary: 'Conflito de horário',
                detail: expect.stringContaining('Conflito com Maria')
            })
        );
    });

    it('emite save com payload e recorrencia=null em avulsa', () => {
        const { actions, emitted } = setup();
        actions.onSave();
        expect(emitted).toHaveLength(1);
        const [name, body] = emitted[0];
        expect(name).toBe('save');
        expect(body.recorrencia).toBe(null);
        expect(body.payload.tipo).toBe('sessao');
    });

    it('emite save com recorrencia preenchida em recorrência', () => {
        const { actions, emitted } = setup({
            recorrenciaType: ref('semanal'),
            qtdSessoesEfetiva: ref(4),
            dataFimCalculada: ref(new Date('2026-06-05'))
        });
        actions.onSave();
        const [, body] = emitted[0];
        expect(body.recorrencia).not.toBe(null);
        expect(body.recorrencia.tipoFreq).toBe('semanal');
        expect(body.recorrencia.qtdSessoes).toBe(4);
    });

    it('inclui editMode/recurrence_id/original_date quando editando série', () => {
        const props = {
            eventRow: { recurrence_id: 'r-1', original_date: '2026-05-15' }
        };
        const { actions, emitted } = setup({ hasSerie: ref(true), editScope: ref('todos') }, props);
        actions.onSave();
        const [, body] = emitted[0];
        expect(body.editMode).toBe('todos');
        expect(body.recurrence_id).toBe('r-1');
        expect(body.original_date).toBe('2026-05-15');
    });

    it('serviceItems e onSaved presentes só em sessão', () => {
        const { actions, emitted, commitmentItems, saveCommitmentItems } = setup();
        commitmentItems.value = [{ service_id: 's-1', quantity: 2 }];
        actions.onSave();
        const [, body] = emitted[0];
        expect(body.serviceItems).toEqual([{ service_id: 's-1', quantity: 2 }]);
        expect(typeof body.onSaved).toBe('function');

        // Simula chamada do onSaved
        body.onSaved('evt-1', { markCustomized: true });
        expect(saveCommitmentItems).toHaveBeenCalledWith('evt-1', expect.any(Array), { markCustomized: true });
    });

    it('serviceItems e onSaved são null em não-session', () => {
        const { actions, emitted } = setup({ isSessionEvent: ref(false) });
        actions.onSave();
        const [, body] = emitted[0];
        expect(body.serviceItems).toBe(null);
        expect(body.onSaved).toBe(null);
    });
});

describe('onDelete', () => {
    it('no-op sem form.id', () => {
        const { actions, emitted } = setup();
        actions.onDelete();
        expect(emitted).toHaveLength(0);
        expect(confirmRequire).not.toHaveBeenCalled();
    });

    it('avulsa: confirm + emit(id)', () => {
        const { composer, actions, emitted } = setup();
        composer.form.value.id = 'evt-1';
        actions.onDelete();
        expect(confirmRequire).toHaveBeenCalled();
        // Aceitar dispara emit
        _confirmAccept();
        expect(emitted).toContainEqual(['delete', 'evt-1']);
    });

    it('série: confirm com escopo + emit({id, editMode, ...})', () => {
        const props = { eventRow: { recurrence_id: 'r-1', original_date: '2026-05-15' } };
        const { composer, actions, emitted } = setup({ hasSerie: ref(true), editScope: ref('este_e_seguintes') }, props);
        composer.form.value.id = 'evt-1';
        actions.onDelete();
        _confirmAccept();
        expect(emitted).toHaveLength(1);
        const [name, body] = emitted[0];
        expect(name).toBe('delete');
        expect(body).toMatchObject({
            id: 'evt-1',
            editMode: 'este_e_seguintes',
            recurrence_id: 'r-1',
            original_date: '2026-05-15'
        });
    });

    it('série + editScope=todos: usa header "Encerrar toda a série"', () => {
        const { composer, actions } = setup({ hasSerie: ref(true), editScope: ref('todos') });
        composer.form.value.id = 'evt-1';
        actions.onDelete();
        const opts = _confirmCalls[0];
        expect(opts.header).toMatch(/Encerrar toda/);
    });
});

describe('onEncerrarSerie', () => {
    it('confirma encerramento e emite com editMode=todos', () => {
        const props = { eventRow: { recurrence_id: 'r-2', original_date: '2026-05-22' } };
        const { composer, actions, emitted } = setup({}, props);
        composer.form.value.id = 'evt-2';
        actions.onEncerrarSerie();
        expect(confirmRequire).toHaveBeenCalled();
        _confirmAccept();
        const [name, body] = emitted[0];
        expect(name).toBe('delete');
        expect(body.editMode).toBe('todos');
        expect(body.recurrence_id).toBe('r-2');
    });
});

describe('Watcher: billingType', () => {
    it('gratuito limpa items, price=0 e campos de convênio', async () => {
        const { composer, commitmentItems } = setup({
            billingType: ref('particular')
        });
        commitmentItems.value = [{ id: 'i-1' }];
        composer.form.value.price = 100;
        composer.form.value.insurance_plan_id = 'plan-1';
        composer.billingType.value = 'gratuito';
        await new Promise((r) => setTimeout(r, 0)); // flush watcher
        expect(commitmentItems.value).toEqual([]);
        expect(composer.form.value.price).toBe(0);
        expect(composer.form.value.insurance_plan_id).toBe(null);
    });

    it('particular limpa só campos de convênio', async () => {
        const { composer, commitmentItems } = setup({ billingType: ref('convenio') });
        commitmentItems.value = [{ id: 'i-1' }];
        composer.form.value.insurance_plan_id = 'plan-1';
        composer.billingType.value = 'particular';
        await new Promise((r) => setTimeout(r, 0));
        // particular preserva itens (não limpa); só limpa convenio
        expect(composer.form.value.insurance_plan_id).toBe(null);
    });

    it('convenio limpa items e servicePickerSel', async () => {
        const { composer, commitmentItems, servicePickerSel } = setup({ billingType: ref('particular') });
        commitmentItems.value = [{ id: 'i-1' }];
        servicePickerSel.value = 'svc-x';
        composer.billingType.value = 'convenio';
        await new Promise((r) => setTimeout(r, 0));
        expect(commitmentItems.value).toEqual([]);
        expect(servicePickerSel.value).toBe(null);
    });
});

describe('Watcher: form.status (cancelado/remarcado)', () => {
    it('NÃO dispara confirm em status comuns (agendado, realizado)', async () => {
        const { composer } = setup({ isEdit: ref(true) });
        composer.form.value.id = 'evt-1';
        composer.form.value.status = 'realizado';
        await new Promise((r) => setTimeout(r, 0));
        expect(confirmRequire).not.toHaveBeenCalled();
    });

    it('dispara confirm em "cancelado"', async () => {
        const { composer } = setup({ isEdit: ref(true) });
        composer.form.value.id = 'evt-1';
        composer.form.value.status = 'cancelado';
        await new Promise((r) => setTimeout(r, 0));
        expect(confirmRequire).toHaveBeenCalled();
        const opts = _confirmCalls[0];
        expect(opts.header).toMatch(/Cancelar/);
    });

    it('dispara confirm em "remarcado"', async () => {
        const { composer } = setup({ isEdit: ref(true) });
        composer.form.value.id = 'evt-1';
        composer.form.value.status = 'remarcado';
        await new Promise((r) => setTimeout(r, 0));
        expect(confirmRequire).toHaveBeenCalled();
        expect(_confirmCalls[0].header).toMatch(/Remarcar/);
    });

    it('reverte status no reject', async () => {
        const { composer } = setup({ isEdit: ref(true) });
        composer.form.value.id = 'evt-1';
        composer.form.value.status = 'agendado';
        await new Promise((r) => setTimeout(r, 0));
        composer.form.value.status = 'cancelado';
        await new Promise((r) => setTimeout(r, 0));
        _confirmReject();
        expect(composer.form.value.status).toBe('agendado');
    });

    it('NÃO dispara se !isEdit', async () => {
        const { composer } = setup({ isEdit: ref(false) });
        composer.form.value.status = 'cancelado';
        await new Promise((r) => setTimeout(r, 0));
        expect(confirmRequire).not.toHaveBeenCalled();
    });

    it('NÃO dispara se _skipStatusWatch ativo', async () => {
        const { composer, actions } = setup({ isEdit: ref(true) });
        composer.form.value.id = 'evt-1';
        actions._skipStatusWatch.value = true;
        composer.form.value.status = 'cancelado';
        await new Promise((r) => setTimeout(r, 0));
        expect(confirmRequire).not.toHaveBeenCalled();
    });
});
