import { describe, expect, it, beforeAll, vi, afterAll } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper';
import FormModel from 'src/components/FormModel.vue';

describe('FormModel', () => {
    const model = 'mockModel';
    const url = 'mockUrl';
    const formInitialData = { mockKey: 'mockVal' };

    describe('modelValue', () => {
        it('should use the provided model', () => {
            const { vm } = mount({ propsData: { model } });
            expect(vm.modelValue).toBe(model);
        });

        it('should use the route meta title when model is not provided', () => {
            const { vm } = mount({});
            expect(vm.modelValue).toBe('formModel_mockTitle');
        });
    });

    describe('onMounted()', () => {
        let mockGet;

        beforeAll(() => {
            mockGet = vi.spyOn(axios, 'get').mockResolvedValue({ data: {} });
        });

        afterAll(() => {
            mockGet.mockRestore();
        });

        it('should not fetch when has formInitialData', () => {
            mount({ propsData: { url, model, autoLoad: true, formInitialData } });
            expect(mockGet).not.toHaveBeenCalled();
        });

        it('should fetch when there is url and auto-load', () => {
            mount({ propsData: { url, model, autoLoad: true } });
            expect(mockGet).toHaveBeenCalled();
        });

        it('should not observe changes', () => {
            const { vm } = mount({
                propsData: { url, model, observeFormChanges: false, formInitialData },
            });

            expect(vm.hasChanges).toBe(true);
            vm.reset();
            expect(vm.hasChanges).toBe(true);
        });

        it('should observe changes', async () => {
            const { vm } = mount({
                propsData: { url, model, formInitialData },
            });
            vm.state.set(model, formInitialData);
            expect(vm.hasChanges).toBe(false);

            vm.formData.mockKey = 'newVal';
            await vm.$nextTick();
            expect(vm.hasChanges).toBe(true);
            vm.formData.mockKey = 'mockVal';
        });
    });

    describe('trimData()', () => {
        let vm;
        beforeAll(() => {
            vm = mount({}).vm;
        });

        it('should trim whitespace from string values', () => {
            const data = { key1: '  value1  ', key2: '  value2  ' };
            const trimmedData = vm.trimData(data);
            expect(trimmedData).toEqual({ key1: 'value1', key2: 'value2' });
        });

        it('should not modify non-string values', () => {
            const data = { key1: 123, key2: true, key3: null, key4: undefined };
            const trimmedData = vm.trimData(data);
            expect(trimmedData).toEqual(data);
        });
    });

    describe('save()', async () => {
        it('should not call if there are not changes', async () => {
            const { vm } = mount({ propsData: { url, model } });

            await vm.save();
            expect(vm.hasChanges).toBe(false);
        });

        it('should call axios.patch with the right data', async () => {
            const spy = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} });
            const { vm } = mount({ propsData: { url, model, formInitialData } });
            vm.formData.mockKey = 'newVal';
            await vm.$nextTick();
            await vm.save();
            expect(spy).toHaveBeenCalled();
            vm.formData.mockKey = 'mockVal';
        });

        it('should call axios.post with the right data', async () => {
            const spy = vi.spyOn(axios, 'post').mockResolvedValue({ data: {} });
            const { vm } = mount({
                propsData: { url, model, formInitialData, urlCreate: 'mockUrlCreate' },
            });
            vm.formData.mockKey = 'newVal';
            await vm.$nextTick();
            await vm.save();
            expect(spy).toHaveBeenCalled();
            vm.formData.mockKey = 'mockVal';
        });

        it('should use the saveFn', async () => {
            const { vm } = mount({
                propsData: { url, model, formInitialData, saveFn: () => {} },
            });
            const spyPatch = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} });
            const spySaveFn = vi.spyOn(vm.$props, 'saveFn');

            vm.formData.mockKey = 'newVal';
            await vm.$nextTick();
            await vm.save();
            expect(spyPatch).not.toHaveBeenCalled();
            expect(spySaveFn).toHaveBeenCalled();
            vm.formData.mockKey = 'mockVal';
        });

        it('should reload the data after save', async () => {
            const { vm } = mount({
                propsData: { url, model, formInitialData, reload: true },
            });
            vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} });

            vm.formData.mockKey = 'newVal';
            await vm.$nextTick();
            await vm.save();
            vm.formData.mockKey = 'mockVal';
        });
    });
});

function mount({ propsData = {} }) {
    return createWrapper(FormModel, {
        propsData,
    });
}
