/**
 * useAgendaEvents.spec.js — T#9
 *
 * Wrapper fino do agendaRepository. Cobertura focada nos contratos:
 *  - rows/loading/error reativos
 *  - delega I/O ao repository (nada de fetch direto)
 *  - sem ownerId, loadMyRange é no-op (proteção tenant scoping)
 *  - error é capturado em loadMyRange e zera rows; create/update/remove propagam
 */
import { describe, it, expect, vi, beforeEach } from 'vitest';

const listMock   = vi.fn();
const createMock = vi.fn();
const updateMock = vi.fn();
const removeMock = vi.fn();

vi.mock('@/features/agenda/services/agendaRepository', () => ({
    listMyAgendaEvents:    (...a) => listMock(...a),
    createAgendaEvento:    (...a) => createMock(...a),
    updateAgendaEvento:    (...a) => updateMock(...a),
    deleteAgendaEvento:    (...a) => removeMock(...a)
}));

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

beforeEach(() => {
    listMock.mockReset();
    createMock.mockReset();
    updateMock.mockReset();
    removeMock.mockReset();
});

describe('useAgendaEvents — estado inicial', () => {
    it('rows vazio, loading false, error null', () => {
        const { rows, loading, error } = useAgendaEvents();
        expect(rows.value).toEqual([]);
        expect(loading.value).toBe(false);
        expect(error.value).toBe(null);
    });
});

describe('loadMyRange', () => {
    it('sem ownerId, não chama o repository (no-op de segurança)', async () => {
        const { rows, loadMyRange } = useAgendaEvents();
        await loadMyRange('2026-01-01', '2026-01-31', null);
        await loadMyRange('2026-01-01', '2026-01-31', '');
        await loadMyRange('2026-01-01', '2026-01-31', undefined);
        expect(listMock).not.toHaveBeenCalled();
        expect(rows.value).toEqual([]);
    });

    it('chama listMyAgendaEvents com payload correto e popula rows', async () => {
        listMock.mockResolvedValue([{ id: 'a' }, { id: 'b' }]);
        const { rows, loading, loadMyRange } = useAgendaEvents();

        const promise = loadMyRange('2026-01-01', '2026-01-31', 'u-1');
        // loading vira true durante a execução
        expect(loading.value).toBe(true);
        await promise;

        expect(listMock).toHaveBeenCalledWith({
            startISO: '2026-01-01',
            endISO: '2026-01-31',
            ownerId: 'u-1'
        });
        expect(rows.value).toEqual([{ id: 'a' }, { id: 'b' }]);
        expect(loading.value).toBe(false);
    });

    it('captura erro, zera rows e seta error.message', async () => {
        listMock.mockRejectedValue(new Error('fetch fail'));
        const { rows, loading, error, loadMyRange } = useAgendaEvents();

        await loadMyRange('2026-01-01', '2026-01-31', 'u-1');

        expect(rows.value).toEqual([]);
        expect(loading.value).toBe(false);
        expect(error.value).toBe('fetch fail');
    });

    it('reseta error em call subsequente bem-sucedida', async () => {
        listMock.mockRejectedValueOnce(new Error('first fail')).mockResolvedValueOnce([{ id: 'x' }]);
        const { error, loadMyRange } = useAgendaEvents();

        await loadMyRange('2026-01-01', '2026-01-31', 'u-1');
        expect(error.value).toBe('first fail');

        await loadMyRange('2026-01-01', '2026-01-31', 'u-1');
        expect(error.value).toBe(null);
    });

    it('error sem message vira fallback string', async () => {
        listMock.mockRejectedValue({}); // sem .message
        const { error, loadMyRange } = useAgendaEvents();
        await loadMyRange('2026-01-01', '2026-01-31', 'u-1');
        expect(error.value).toBe('Erro ao carregar eventos');
    });
});

describe('create / update / remove — delegação pura', () => {
    it('create encaminha payload e retorna o resultado do repository', async () => {
        createMock.mockResolvedValue({ id: 'new', titulo: 'X' });
        const { create } = useAgendaEvents();
        const result = await create({ titulo: 'X' });
        expect(createMock).toHaveBeenCalledWith({ titulo: 'X' });
        expect(result).toEqual({ id: 'new', titulo: 'X' });
    });

    it('update encaminha id+patch e retorna o resultado', async () => {
        updateMock.mockResolvedValue({ id: '42', titulo: 'updated' });
        const { update } = useAgendaEvents();
        const result = await update('42', { titulo: 'updated' });
        expect(updateMock).toHaveBeenCalledWith('42', { titulo: 'updated' });
        expect(result).toEqual({ id: '42', titulo: 'updated' });
    });

    it('remove encaminha id (sem retorno)', async () => {
        removeMock.mockResolvedValue(undefined);
        const { remove } = useAgendaEvents();
        const result = await remove('99');
        expect(removeMock).toHaveBeenCalledWith('99');
        expect(result).toBeUndefined();
    });

    it('create propaga erro (não engole)', async () => {
        createMock.mockRejectedValue(new Error('insert blocked by RLS'));
        const { create } = useAgendaEvents();
        await expect(create({ titulo: 'X' })).rejects.toThrow(/RLS/);
    });

    it('update propaga erro', async () => {
        updateMock.mockRejectedValue(new Error('not found'));
        const { update } = useAgendaEvents();
        await expect(update('1', { x: 1 })).rejects.toThrow(/not found/);
    });

    it('remove propaga erro', async () => {
        removeMock.mockRejectedValue(new Error('cascade fail'));
        const { remove } = useAgendaEvents();
        await expect(remove('1')).rejects.toThrow(/cascade/);
    });
});

describe('isolamento entre instâncias', () => {
    it('cada useAgendaEvents() retorna refs independentes', async () => {
        listMock.mockResolvedValue([{ id: 'a' }]);
        const a = useAgendaEvents();
        const b = useAgendaEvents();

        await a.loadMyRange('2026-01-01', '2026-01-31', 'u-1');

        expect(a.rows.value).toEqual([{ id: 'a' }]);
        expect(b.rows.value).toEqual([]); // b não foi tocado
    });
});
