/**
 * useAgendaEventPickerBilling.spec.js — A66 sub-sessão 1C-ii-a
 *
 * Cobre handlers de patient picker + billing items + 2 watchers
 * (form.commitment_id, form.insurance_plan_id).
 *
 * Mock estratégia: monta um composer fake + actions fake com refs/computeds
 * mínimos. Mock supabase.from('patients') pra loadPatients.
 */
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ref, computed } from 'vue';

// ── Mocks ─────────────────────────────────────────────────────
let _supabaseSelectArgs = null;
const _patientsResult = { data: [], error: null };
function makeQ() {
    // O loadPatients faz: from().select().order().limit() e DEPOIS adiciona
    // .eq() condicionalmente, finalizando com `await q`. Pra suportar isso,
    // o mock retorna o mesmo `q` em todos os métodos da chain e implementa
    // `then` (thenable) pra ser awaitable.
    const q = {
        select: (...args) => {
            _supabaseSelectArgs = args;
            return q;
        },
        eq: () => q,
        order: () => q,
        limit: () => q,
        then: (resolve) => resolve(_patientsResult)
    };
    return q;
}
vi.mock('@/lib/supabase/client', () => ({
    supabase: {
        from: () => makeQ()
    }
}));

function resetPatientsResult({ data = [], error = null } = {}) {
    _patientsResult.data = data;
    _patientsResult.error = error;
}

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

// ── Helpers ───────────────────────────────────────────────────
function makeComposer(overrides = {}) {
    const form = ref({
        commitment_id: null,
        paciente_id: null,
        paciente_nome: '',
        paciente_avatar: '',
        extra_fields: {},
        price: null,
        insurance_plan_id: null,
        insurance_plan_service_id: null,
        insurance_value: null,
        insurance_guide_number: null,
        duracaoMin: 50,
        ...(overrides.formExtra || {})
    });
    return {
        form,
        billingType: ref('particular'),
        isEdit: ref(false),
        visible: ref(true),
        step: ref(1),
        requiresPatient: ref(true),
        allowBack: ref(true),
        ...overrides
    };
}
function makeActions() {
    return {
        _restoringConvenio: ref(false),
        samePatientConflict: ref(null)
    };
}

function setup(overrides = {}, propsOverrides = {}) {
    _supabaseSelectArgs = null;
    // NÃO resetamos _patientsResult aqui pra permitir que o teste pré-popule.
    // Cada `it` chama resetPatientsResult() explicitamente quando precisa.

    const composer = overrides.composer ?? makeComposer();
    const actions = overrides.actions ?? makeActions();
    const commitmentItems = overrides.commitmentItems ?? ref([]);
    const servicePickerSel = overrides.servicePickerSel ?? ref(null);
    const selectedPlanService = overrides.selectedPlanService ?? ref(null);
    const services = overrides.services ?? ref([]);
    const loadServices = vi.fn().mockResolvedValue();
    const getDefaultPrice = overrides.getDefaultPrice ?? vi.fn(() => null);
    const planServices = overrides.planServices ?? computed(() => []);
    const loadActiveDiscount = overrides.loadActiveDiscount ?? vi.fn().mockResolvedValue(null);
    const _csLoadItems = overrides._csLoadItems ?? vi.fn().mockResolvedValue([]);
    const _csLoadItemsOrTemplate = overrides._csLoadItemsOrTemplate ?? vi.fn().mockResolvedValue([]);
    const isDynamic = overrides.isDynamic ?? computed(() => false);
    const props = {
        ownerId: 'owner-1',
        tenantId: 'tenant-1',
        eventRow: null,
        agendaSettings: { session_duration_min: 50 },
        restrictPatientsToOwner: false,
        patientScopeOwnerId: null,
        newPatientRoute: '',
        ...propsOverrides
    };

    const result = useAgendaEventPickerBilling({
        composer,
        actions,
        commitmentItems,
        servicePickerSel,
        selectedPlanService,
        services,
        loadServices,
        getDefaultPrice,
        planServices,
        loadActiveDiscount,
        _csLoadItems,
        _csLoadItemsOrTemplate,
        isDynamic,
        props
    });

    return {
        composer,
        actions,
        commitmentItems,
        servicePickerSel,
        selectedPlanService,
        services,
        loadServices,
        getDefaultPrice,
        planServices,
        loadActiveDiscount,
        _csLoadItems,
        _csLoadItemsOrTemplate,
        isDynamic,
        props,
        ...result
    };
}

// ════════════════════════════════════════════════════════════════════
describe('addItem', () => {
    it('no-op sem service.id', async () => {
        const { commitmentItems, addItem } = setup();
        await addItem(null);
        await addItem({});
        expect(commitmentItems.value).toEqual([]);
    });

    it('adiciona item novo com final_price calculado', async () => {
        const { commitmentItems, addItem } = setup();
        await addItem({ id: 's-1', name: 'Sessão', price: 100 });
        expect(commitmentItems.value).toHaveLength(1);
        expect(commitmentItems.value[0]).toMatchObject({
            service_id: 's-1',
            service_name: 'Sessão',
            quantity: 1,
            unit_price: 100,
            discount_pct: 0,
            discount_flat: 0,
            final_price: 100
        });
    });

    it('incrementa quantity quando service_id já existe', async () => {
        const { commitmentItems, addItem } = setup();
        await addItem({ id: 's-1', name: 'Sessão', price: 100 });
        await addItem({ id: 's-1', name: 'Sessão', price: 100 });
        await addItem({ id: 's-1', name: 'Sessão', price: 100 });
        expect(commitmentItems.value).toHaveLength(1);
        expect(commitmentItems.value[0].quantity).toBe(3);
        expect(commitmentItems.value[0].final_price).toBe(300);
    });

    it('aplica desconto ativo do paciente', async () => {
        const composer = makeComposer({ formExtra: { paciente_id: 'p-1' } });
        const loadActiveDiscount = vi.fn().mockResolvedValue({ discount_pct: 10, discount_flat: 5 });
        const { commitmentItems, addItem } = setup({ composer, loadActiveDiscount });
        await addItem({ id: 's-1', name: 'X', price: 100 });
        expect(loadActiveDiscount).toHaveBeenCalledWith('owner-1', 'p-1');
        // 100 - 10% = 90 - 5 = 85
        expect(commitmentItems.value[0].final_price).toBe(85);
    });

    it('sem patient_id NÃO chama loadActiveDiscount', async () => {
        const { addItem, loadActiveDiscount } = setup();
        await addItem({ id: 's-1', name: 'X', price: 100 });
        expect(loadActiveDiscount).not.toHaveBeenCalled();
    });
});

describe('removeItem', () => {
    it('remove item por índice', () => {
        const items = ref([
            { service_id: 'a', quantity: 1, final_price: 100 },
            { service_id: 'b', quantity: 1, final_price: 200 }
        ]);
        const { removeItem, commitmentItems } = setup({ commitmentItems: items });
        removeItem(0);
        expect(commitmentItems.value).toHaveLength(1);
        expect(commitmentItems.value[0].service_id).toBe('b');
    });

    it('lista vazia em modo dynamic restaura duração padrão', () => {
        const composer = makeComposer({ formExtra: { duracaoMin: 30 } });
        const isDynamic = computed(() => true);
        const items = ref([{ service_id: 'a', quantity: 1, final_price: 100 }]);
        const { removeItem } = setup({ composer, isDynamic, commitmentItems: items });
        removeItem(0);
        expect(composer.form.value.duracaoMin).toBe(50); // session_duration_min default
    });

    it('NÃO restaura duração se !isDynamic', () => {
        const composer = makeComposer({ formExtra: { duracaoMin: 30 } });
        const items = ref([{ service_id: 'a', quantity: 1, final_price: 100 }]);
        const { removeItem } = setup({ composer, commitmentItems: items });
        removeItem(0);
        expect(composer.form.value.duracaoMin).toBe(30);
    });
});

describe('onItemChange', () => {
    it('recalcula final_price baseado em quantity/discounts', () => {
        const { onItemChange } = setup();
        const item = { unit_price: 100, quantity: 2, discount_pct: 10, discount_flat: 0, final_price: 0 };
        onItemChange(item);
        expect(item.final_price).toBe(180); // 200 - 10% = 180
    });
});

describe('onProcedureSelect', () => {
    it('seta plan_service_id e atualiza insurance_value', () => {
        const planServices = computed(() => [{ id: 'ps-1', value: 250.5 }]);
        const composer = makeComposer();
        const { onProcedureSelect } = setup({ composer, planServices });
        onProcedureSelect('ps-1');
        expect(composer.form.value.insurance_plan_service_id).toBe('ps-1');
        expect(composer.form.value.insurance_value).toBe(250.5);
    });

    it('null limpa insurance_value', () => {
        const composer = makeComposer({ formExtra: { insurance_value: 100 } });
        const { onProcedureSelect } = setup({ composer });
        onProcedureSelect(null);
        expect(composer.form.value.insurance_plan_service_id).toBe(null);
        expect(composer.form.value.insurance_value).toBe(null);
    });

    it('id desconhecido limpa insurance_value', () => {
        const planServices = computed(() => [{ id: 'ps-1', value: 250 }]);
        const composer = makeComposer({ formExtra: { insurance_value: 100 } });
        const { onProcedureSelect } = setup({ composer, planServices });
        onProcedureSelect('ps-x');
        expect(composer.form.value.insurance_plan_service_id).toBe('ps-x');
        expect(composer.form.value.insurance_value).toBe(null);
    });
});

describe('selectCommitment', () => {
    it('seta commitment_id, reseta extra_fields, vai pra step 2', () => {
        const composer = makeComposer();
        const { selectCommitment } = setup({ composer });
        selectCommitment({ id: 'c-1', name: 'X', fields: [{ key: 'idade' }, { key: 'peso' }] });
        expect(composer.form.value.commitment_id).toBe('c-1');
        expect(composer.form.value.extra_fields).toEqual({ idade: '', peso: '' });
        expect(composer.step.value).toBe(2);
    });

    it('no-op sem id', () => {
        const composer = makeComposer();
        const { selectCommitment } = setup({ composer });
        composer.step.value = 1;
        selectCommitment(null);
        selectCommitment({});
        expect(composer.step.value).toBe(1);
    });
});

describe('goBack', () => {
    it('volta pra step 1 e limpa commitment + paciente', () => {
        const composer = makeComposer({
            formExtra: { commitment_id: 'c-1', paciente_id: 'p-1', paciente_nome: 'Ana' }
        });
        composer.step.value = 2;
        const { goBack } = setup({ composer });
        goBack();
        expect(composer.step.value).toBe(1);
        expect(composer.form.value.commitment_id).toBe(null);
        expect(composer.form.value.paciente_id).toBe(null);
        expect(composer.form.value.paciente_nome).toBe('');
    });

    it('no-op em edição', () => {
        const composer = makeComposer();
        composer.isEdit.value = true;
        composer.step.value = 2;
        const { goBack } = setup({ composer });
        goBack();
        expect(composer.step.value).toBe(2);
    });

    it('no-op com !allowBack', () => {
        const composer = makeComposer();
        composer.allowBack.value = false;
        composer.step.value = 2;
        const { goBack } = setup({ composer });
        goBack();
        expect(composer.step.value).toBe(2);
    });
});

describe('selectPaciente / clearPaciente', () => {
    it('selectPaciente preenche form e fecha picker', () => {
        const composer = makeComposer();
        const { selectPaciente, pacientePickerOpen } = setup({ composer });
        pacientePickerOpen.value = true;
        selectPaciente({ id: 'p-1', nome: 'Ana', avatar_url: 'url' });
        expect(composer.form.value.paciente_id).toBe('p-1');
        expect(composer.form.value.paciente_nome).toBe('Ana');
        expect(composer.form.value.paciente_avatar).toBe('url');
        expect(pacientePickerOpen.value).toBe(false);
    });

    it('selectPaciente no-op sem id', () => {
        const composer = makeComposer();
        const { selectPaciente } = setup({ composer });
        selectPaciente(null);
        selectPaciente({});
        expect(composer.form.value.paciente_id).toBe(null);
    });

    it('selectPaciente copia status pro form quando Ativo', () => {
        const composer = makeComposer();
        const { selectPaciente } = setup({ composer });
        selectPaciente({ id: 'p-1', nome: 'Ana', status: 'Ativo' });
        expect(composer.form.value.paciente_id).toBe('p-1');
        expect(composer.form.value.paciente_status).toBe('Ativo');
    });

    it('selectPaciente bloqueia paciente Arquivado (defesa em camadas)', () => {
        const composer = makeComposer();
        const { selectPaciente, pacientePickerOpen } = setup({ composer });
        pacientePickerOpen.value = true;
        selectPaciente({ id: 'p-arq', nome: 'Ana', status: 'Arquivado' });
        // Form nao deve ter sido tocado
        expect(composer.form.value.paciente_id).toBe(null);
        expect(composer.form.value.paciente_status).toBeFalsy();
        // Picker permanece aberto pro user escolher outro
        expect(pacientePickerOpen.value).toBe(true);
    });

    it('selectPaciente bloqueia paciente Inativo', () => {
        const composer = makeComposer();
        const { selectPaciente } = setup({ composer });
        selectPaciente({ id: 'p-ina', nome: 'Bruno', status: 'Inativo' });
        expect(composer.form.value.paciente_id).toBe(null);
    });

    it('clearPaciente limpa form + samePatientConflict', () => {
        const composer = makeComposer({
            formExtra: { paciente_id: 'p-1', paciente_nome: 'Ana', paciente_avatar: 'url' }
        });
        const actions = makeActions();
        actions.samePatientConflict.value = { id: 'evt-x' };
        const { clearPaciente } = setup({ composer, actions });
        clearPaciente();
        expect(composer.form.value.paciente_id).toBe(null);
        expect(composer.form.value.paciente_nome).toBe('');
        expect(actions.samePatientConflict.value).toBe(null);
    });
});

describe('openPacientePicker', () => {
    it('abre picker e dispara loadPatients', () => {
        const composer = makeComposer();
        composer.requiresPatient.value = true;
        const { openPacientePicker, pacientePickerOpen } = setup({ composer });
        openPacientePicker();
        expect(pacientePickerOpen.value).toBe(true);
    });

    it('no-op quando NÃO requiresPatient', () => {
        const composer = makeComposer();
        composer.requiresPatient.value = false;
        const { openPacientePicker, pacientePickerOpen } = setup({ composer });
        openPacientePicker();
        expect(pacientePickerOpen.value).toBe(false);
    });
});

describe('clearPatientsCache', () => {
    it('zera patients, search e error', () => {
        const { clearPatientsCache, patients, pacienteSearch, pacientesError } = setup();
        patients.value = [{ id: 'p-1' }];
        pacienteSearch.value = 'foo';
        pacientesError.value = 'err';
        clearPatientsCache();
        expect(patients.value).toEqual([]);
        expect(pacienteSearch.value).toBe('');
        expect(pacientesError.value).toBe('');
    });
});

describe('loadPatients', () => {
    it('faz fetch e mapeia resultado', async () => {
        const { loadPatients, patients } = setup();
        resetPatientsResult({
            data: [{ id: 'p-1', nome_completo: 'Ana', email_principal: 'a@x', telefone: '11', status: 'Ativo', avatar_url: 'u' }]
        });
        await loadPatients(true);
        expect(patients.value).toHaveLength(1);
        expect(patients.value[0]).toMatchObject({
            id: 'p-1',
            nome: 'Ana',
            email: 'a@x',
            telefone: '11'
        });
    });

    it('skip quando já tem cache e !force', async () => {
        const { loadPatients, patients } = setup();
        patients.value = [{ id: 'cached' }];
        await loadPatients(false);
        expect(patients.value).toEqual([{ id: 'cached' }]);
    });

    it('error → setta pacientesError e zera lista', async () => {
        const { loadPatients, patients, pacientesError } = setup();
        resetPatientsResult({ data: null, error: new Error('boom') });
        await loadPatients(true);
        expect(patients.value).toEqual([]);
        expect(pacientesError.value).toBe('boom');
    });
});

describe('applyDefaultPrice', () => {
    it('skip em billingType=particular', () => {
        const composer = makeComposer();
        composer.billingType.value = 'particular';
        const getDefaultPrice = vi.fn(() => 100);
        const { applyDefaultPrice } = setup({ composer, getDefaultPrice });
        applyDefaultPrice();
        expect(composer.form.value.price).toBe(null);
    });

    it('skip em edição', () => {
        const composer = makeComposer();
        composer.billingType.value = 'gratuito';
        composer.isEdit.value = true;
        const getDefaultPrice = vi.fn(() => 100);
        const { applyDefaultPrice } = setup({ composer, getDefaultPrice });
        applyDefaultPrice();
        expect(composer.form.value.price).toBe(null);
    });

    it('aplica em criação + billingType != particular', () => {
        const composer = makeComposer();
        composer.billingType.value = 'gratuito';
        const getDefaultPrice = vi.fn(() => 75);
        const { applyDefaultPrice } = setup({ composer, getDefaultPrice });
        applyDefaultPrice();
        expect(composer.form.value.price).toBe(75);
    });
});

describe('Watcher: form.commitment_id (auto-fill price)', () => {
    it('dispara em criação + visível: ensureServices + applyDefaultPrice', async () => {
        const composer = makeComposer();
        // applyDefaultPrice skip se billingType=particular (default); usar 'gratuito'
        composer.billingType.value = 'gratuito';
        const getDefaultPrice = vi.fn(() => 80);
        const { loadServices, composer: c } = setup({ composer, getDefaultPrice });
        c.form.value.commitment_id = 'c-1';
        await new Promise((r) => setTimeout(r, 0));
        expect(loadServices).toHaveBeenCalled();
        expect(c.form.value.price).toBe(80);
    });

    it('NÃO dispara em edição', async () => {
        const composer = makeComposer();
        composer.isEdit.value = true;
        const { loadServices } = setup({ composer });
        composer.form.value.commitment_id = 'c-1';
        await new Promise((r) => setTimeout(r, 0));
        expect(loadServices).not.toHaveBeenCalled();
    });

    it('NÃO dispara quando dialog fechado (!visible)', async () => {
        const composer = makeComposer();
        composer.visible.value = false;
        const { loadServices } = setup({ composer });
        composer.form.value.commitment_id = 'c-1';
        await new Promise((r) => setTimeout(r, 0));
        expect(loadServices).not.toHaveBeenCalled();
    });
});

describe('Watcher: form.insurance_plan_id', () => {
    it('seleciona convênio: limpa items + servicePickerSel', async () => {
        const composer = makeComposer();
        const items = ref([{ service_id: 'a' }]);
        const sps = ref('svc-x');
        const { commitmentItems, servicePickerSel } = setup({
            composer,
            commitmentItems: items,
            servicePickerSel: sps
        });
        composer.form.value.insurance_plan_id = 'plan-1';
        await new Promise((r) => setTimeout(r, 0));
        expect(commitmentItems.value).toEqual([]);
        expect(servicePickerSel.value).toBe(null);
    });

    it('desmarca convênio: limpa insurance_value e guide', async () => {
        const composer = makeComposer({
            formExtra: { insurance_value: 100, insurance_guide_number: 'X' }
        });
        const { composer: c } = setup({ composer });
        c.form.value.insurance_plan_id = 'plan-1';
        await new Promise((r) => setTimeout(r, 0));
        c.form.value.insurance_plan_id = null;
        await new Promise((r) => setTimeout(r, 0));
        expect(c.form.value.insurance_value).toBe(null);
        expect(c.form.value.insurance_guide_number).toBe(null);
    });

    it('ignorado quando _restoringConvenio', async () => {
        const composer = makeComposer();
        const actions = makeActions();
        actions._restoringConvenio.value = true;
        const items = ref([{ service_id: 'a' }]);
        const { commitmentItems } = setup({
            composer,
            actions,
            commitmentItems: items
        });
        composer.form.value.insurance_plan_id = 'plan-1';
        await new Promise((r) => setTimeout(r, 0));
        // Restoração ativa: não toca em commitmentItems
        expect(commitmentItems.value).toHaveLength(1);
    });
});

describe('_loadCommitmentItemsForEvent', () => {
    it('sem eventId nem ruleId: limpa items, billingType=particular', async () => {
        const composer = makeComposer();
        const items = ref([{ service_id: 'a' }]);
        const { _loadCommitmentItemsForEvent, commitmentItems } = setup({
            composer,
            commitmentItems: items
        });
        await _loadCommitmentItemsForEvent(null);
        expect(commitmentItems.value).toEqual([]);
        expect(composer.billingType.value).toBe('particular');
    });

    it('eventRow com insurance_plan_id: aplica convenio', async () => {
        const composer = makeComposer();
        const props = {
            ownerId: 'owner-1',
            tenantId: 't-1',
            agendaSettings: { session_duration_min: 50 },
            eventRow: {
                insurance_plan_id: 'plan-1',
                insurance_guide_number: 'G-1',
                insurance_value: 200,
                insurance_plan_service_id: 'ps-1'
            }
        };
        const { _loadCommitmentItemsForEvent } = setup({ composer }, props);
        await _loadCommitmentItemsForEvent('evt-1');
        // Espera nextTick interno
        await new Promise((r) => setTimeout(r, 10));
        expect(composer.billingType.value).toBe('convenio');
        expect(composer.form.value.insurance_plan_id).toBe('plan-1');
        expect(composer.form.value.insurance_value).toBe(200);
    });

    it('com items carregados: billingType=particular', async () => {
        const composer = makeComposer();
        const _csLoadItems = vi.fn().mockResolvedValue([{ service_id: 's-1', final_price: 100 }]);
        const { _loadCommitmentItemsForEvent, commitmentItems } = setup({
            composer,
            _csLoadItems
        });
        await _loadCommitmentItemsForEvent('evt-1');
        expect(commitmentItems.value).toHaveLength(1);
        expect(composer.billingType.value).toBe('particular');
    });

    it('sem items: billingType=gratuito', async () => {
        const composer = makeComposer();
        const _csLoadItems = vi.fn().mockResolvedValue([]);
        const { _loadCommitmentItemsForEvent } = setup({ composer, _csLoadItems });
        await _loadCommitmentItemsForEvent('evt-1');
        expect(composer.billingType.value).toBe('gratuito');
    });

    it('error: items=[], billingType=gratuito', async () => {
        const composer = makeComposer();
        const _csLoadItems = vi.fn().mockRejectedValue(new Error('boom'));
        const { _loadCommitmentItemsForEvent, commitmentItems } = setup({
            composer,
            _csLoadItems
        });
        await _loadCommitmentItemsForEvent('evt-1');
        expect(commitmentItems.value).toEqual([]);
        expect(composer.billingType.value).toBe('gratuito');
    });
});

describe('ensureServicesLoaded', () => {
    it('carrega só uma vez (gate)', async () => {
        const { ensureServicesLoaded, loadServices } = setup();
        await ensureServicesLoaded();
        await ensureServicesLoaded();
        await ensureServicesLoaded();
        expect(loadServices).toHaveBeenCalledTimes(1);
    });

    it('skip sem ownerId', async () => {
        const { ensureServicesLoaded, loadServices } = setup({}, { ownerId: '' });
        await ensureServicesLoaded();
        expect(loadServices).not.toHaveBeenCalled();
    });

    it('resetServicesGate permite re-load', async () => {
        const { ensureServicesLoaded, resetServicesGate, loadServices } = setup();
        await ensureServicesLoaded();
        resetServicesGate();
        await ensureServicesLoaded();
        expect(loadServices).toHaveBeenCalledTimes(2);
    });
});
