import { flushPromises, mount } from '@vue/test-utils';
import { defineComponent, h } from 'vue';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { KviewToastProvider } from '@kview/ui-core/vue';
import {
    buildExcelProtocolUrl,
    useApplicationExcelLogin,
} from '@/composables/useApplicationExcelLogin';

const launchProtocol = vi.fn();

const ExcelHarness = defineComponent({
    name: 'ExcelHarness',
    setup() {
        const excel = useApplicationExcelLogin('/api/protocol-token/create', {
            launchProtocol,
        });

        return () => h('div', [
            h('button', {
                type: 'button',
                disabled: excel.pending.value,
                'aria-busy': excel.pending.value ? 'true' : undefined,
                onClick: excel.request,
            }, 'Excel Login'),
            h('button', {
                type: 'button',
                disabled: excel.pending.value,
                'aria-busy': excel.pending.value ? 'true' : undefined,
                onClick: excel.request,
            }, 'Excel menu action'),
        ]);
    },
});

const mountExcel = () => mount(KviewToastProvider, {
    attachTo: document.body,
    slots: {
        default: () => h(ExcelHarness),
    },
});

afterEach(() => {
    document.body.innerHTML = '';
    launchProtocol.mockReset();
    vi.restoreAllMocks();
    vi.useRealTimers();
});

describe('Vue Excel Login owner', () => {
    it('shares one pending request, launches the encoded protocol and reports success', async () => {
        let resolveRequest!: (response: Response) => void;
        const fetchMock = vi.fn(() => new Promise<Response>((resolve) => {
            resolveRequest = resolve;
        }));
        vi.stubGlobal('fetch', fetchMock);
        const wrapper = mountExcel();

        try {
            const buttons = wrapper.findAll('button').filter((button) => (
                button.text().startsWith('Excel')
            ));
            await buttons[0].trigger('click');

            expect(fetchMock).toHaveBeenCalledOnce();
            expect(fetchMock).toHaveBeenCalledWith('/api/protocol-token/create', expect.objectContaining({
                method: 'GET',
                credentials: 'same-origin',
                headers: {
                    Accept: 'application/json',
                    'X-Requested-With': 'XMLHttpRequest',
                },
            }));
            expect(buttons[0].attributes('aria-busy')).toBe('true');
            expect(buttons[1].attributes('aria-busy')).toBe('true');
            expect(document.body.querySelector('.ui-toast--info')?.textContent).toContain(
                'Preparing a secure desktop login token',
            );

            await buttons[1].trigger('click');
            expect(fetchMock).toHaveBeenCalledOnce();

            resolveRequest({
                ok: true,
                json: async () => ({
                    token: 'token /+?&',
                    url: 'https://kview-investor.test/base?a=1&b=2',
                }),
            } as Response);
            await flushPromises();

            expect(launchProtocol).toHaveBeenCalledOnce();
            expect(launchProtocol).toHaveBeenCalledWith(
                'lncom://token=token%20%2F%2B%3F%26'
                + '&url=https%3A%2F%2Fkview-investor.test%2Fbase%3Fa%3D1%26b%3D2'
                + '&encoded_url=https%3A%2F%2Fkview-investor.test%2Fbase%3Fa%3D1%26b%3D2',
            );
            expect(document.body.querySelector('.ui-toast--success')?.textContent).toContain('Excel login ready');
            expect(buttons[0].attributes('aria-busy')).toBeUndefined();
            expect(buttons[1].attributes('aria-busy')).toBeUndefined();
        } finally {
            wrapper.unmount();
        }
    });

    it('reports server errors and timeout aborts without launching the protocol', async () => {
        const fetchMock = vi.fn()
            .mockResolvedValueOnce({ ok: false } as Response)
            .mockImplementationOnce((_url: string, init: RequestInit) => new Promise((_resolve, reject) => {
                init.signal?.addEventListener('abort', () => {
                    reject(new DOMException('Aborted', 'AbortError'));
                });
            }));
        vi.stubGlobal('fetch', fetchMock);
        const first = mountExcel();

        await first.findAll('button')[0].trigger('click');
        await flushPromises();

        expect(document.body.querySelector('.ui-toast--error')?.textContent).toContain('Refresh the page and try again');
        expect(launchProtocol).not.toHaveBeenCalled();
        first.unmount();
        document.body.innerHTML = '';

        vi.useFakeTimers();
        const second = mountExcel();
        await second.findAll('button')[0].trigger('click');
        await vi.advanceTimersByTimeAsync(10_000);
        await flushPromises();

        expect(document.body.querySelector('.ui-toast--error')?.textContent).toContain(
            'The Excel add-in login request timed out',
        );
        expect(launchProtocol).not.toHaveBeenCalled();
        second.unmount();
    });

    it('explains when Excel login is unavailable and does not launch the add-in', async () => {
        vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
            ok: false,
            status: 503,
            json: async () => ({ error: 'excel_login_unavailable' }),
        }));
        const wrapper = mountExcel();
        await wrapper.findAll('button')[0].trigger('click');
        await flushPromises();
        expect(document.body.querySelector('.ui-toast--error')?.textContent).toContain(
            'Excel login is temporarily unavailable. Please contact your administrator.',
        );
        expect(launchProtocol).not.toHaveBeenCalled();
        wrapper.unmount();
    });

    it('builds the exact legacy-compatible protocol URL', () => {
        expect(buildExcelProtocolUrl({ token: 'a b', url: 'https://x.test/?a=1&b=2' })).toBe(
            'lncom://token=a%20b'
            + '&url=https%3A%2F%2Fx.test%2F%3Fa%3D1%26b%3D2'
            + '&encoded_url=https%3A%2F%2Fx.test%2F%3Fa%3D1%26b%3D2',
        );
    });
});
