import {
    flushPromises,
    mount,
} from '@vue/test-utils';
import { KviewCombobox } from '@kview/ui-core/vue';
import {
    afterEach,
    describe,
    expect,
    it,
    vi,
} from 'vitest';
import RecordAutocomplete from '@/components/forms/RecordAutocomplete.vue';
import type { RecordLookupOption } from '@/types/records';

afterEach(() => {
    vi.useRealTimers();
    vi.unstubAllGlobals();
    document.body.innerHTML = '';
});

describe('RecordAutocomplete', () => {
    it('keeps a preselected option visible and submits only its identifier', () => {
        const initialOption: RecordLookupOption = {
            value: 'fund-1',
            label: 'Demo Alpha Growth IV',
            meta: 'Luxembourg SCSp',
            data: { currency: 'EUR' },
        };
        const wrapper = mount(RecordAutocomplete, {
            props: {
                name: 'fund_id',
                modelValue: 'fund-1',
                lookupUrl: '/lookups/funds',
                initialOption,
                label: 'Fund',
            },
        });
        const combobox = wrapper.getComponent(KviewCombobox);

        expect(combobox.props('query')).toBe('Demo Alpha Growth IV');
        expect(combobox.props('options')).toEqual([initialOption]);
        expect(wrapper.get('input[type="hidden"]').attributes('name')).toBe('fund_id');
        expect(wrapper.get('input[type="hidden"]').attributes('value')).toBe('fund-1');
    });

    it('debounces remote work, aborts stale requests, and emits selected metadata', async () => {
        vi.useFakeTimers();

        const requests: Array<{
            url: URL;
            signal: AbortSignal;
            resolve: (value: Response) => void;
        }> = [];
        const fetchMock = vi.fn((input: URL | RequestInfo, init?: RequestInit) => new Promise<Response>((resolve) => {
            requests.push({
                url: input instanceof URL ? input : new URL(String(input), window.location.origin),
                signal: init?.signal as AbortSignal,
                resolve,
            });
        }));

        vi.stubGlobal('fetch', fetchMock);

        const wrapper = mount(RecordAutocomplete, {
            props: {
                name: 'investor_id',
                lookupUrl: '/lookups/investors',
                debounceMs: 200,
                label: 'Investor',
            },
        });
        const combobox = wrapper.getComponent(KviewCombobox);

        combobox.vm.$emit('update:query', 'alpha');
        await vi.advanceTimersByTimeAsync(200);
        expect(requests).toHaveLength(1);
        expect(requests[0]?.url.searchParams.get('q')).toBe('alpha');

        combobox.vm.$emit('update:query', 'beta');
        expect(requests[0]?.signal.aborted).toBe(true);
        await vi.advanceTimersByTimeAsync(200);
        expect(requests).toHaveLength(2);

        requests[1]?.resolve({
            ok: true,
            json: async () => [{
                id: 'investor-2',
                name: 'Beta Pension Trust',
                full_name: 'Beta Pension Trust plc',
                country: 'GB',
            }],
        } as Response);
        await flushPromises();

        const option = wrapper.getComponent(KviewCombobox).props('options')[0] as RecordLookupOption;

        expect(option).toMatchObject({
            value: 'investor-2',
            label: 'Beta Pension Trust',
            meta: 'Beta Pension Trust plc',
        });
        expect(option.data).toMatchObject({ country: 'GB' });

        wrapper.getComponent(KviewCombobox).vm.$emit('update:modelValue', option.value);
        wrapper.getComponent(KviewCombobox).vm.$emit('change', option.value, option);

        expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['investor-2']);
        expect(wrapper.emitted('selected')?.at(-1)).toEqual([option]);
    });

    it('hydrates an identifier without an initial option', async () => {
        const fetchMock = vi.fn().mockResolvedValueOnce({
            ok: true,
            json: async () => [{ id: 'gp-1', name: 'Arcadia Ventures' }],
        });

        vi.stubGlobal('fetch', fetchMock);

        const wrapper = mount(RecordAutocomplete, {
            props: {
                name: 'gp_id',
                modelValue: 'gp-1',
                lookupUrl: '/lookups/gps',
                debounceMs: 0,
            },
        });

        await flushPromises();
        expect(fetchMock.mock.calls[0]?.[0].searchParams.get('id')).toBe('gp-1');
        expect(wrapper.getComponent(KviewCombobox).props('query')).toBe('Arcadia Ventures');
    });

    it('reports lookup failures inline', async () => {
        const fetchMock = vi.fn().mockRejectedValue(new Error('offline'));

        vi.stubGlobal('fetch', fetchMock);

        const wrapper = mount(RecordAutocomplete, {
            props: {
                name: 'gp_id',
                lookupUrl: '/lookups/gps',
                debounceMs: 0,
            },
        });

        wrapper.getComponent(KviewCombobox).vm.$emit('update:query', 'northstar');
        await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
        await vi.waitFor(() => {
            expect(wrapper.getComponent(KviewCombobox).props('error')).toBe('Unable to load results. Try again.');
        });

        expect(wrapper.getComponent(KviewCombobox).props('error')).toBe('Unable to load results. Try again.');
    });
});
