import { afterAll, beforeEach, jest } from '@jest/globals';

const abortableFetch = (url, { signal }={}) => new Promise((res,rej) => {
    if (signal instanceof AbortSignal) {
        const handler = () => rej(new DOMException('Aborted', 'AbortError'));
        if (signal.aborted) {
            handler();
        } else {
            signal.addEventListener('abort', handler);
        }
    }
});

const MockFetch = {

    setUp() {
        this._savedFetch = global.fetch;
        global.fetch = jest.fn();
    },
    tearDown() { global.fetch = this._savedFetch; },
    mockClear() { global.fetch.mockClear(); },
    mockReset() { global.fetch.mockReset(); },

    jsonResponse: (data, options = {}) => ({
        ok: true,
        status: 200,
        json: () => Promise.resolve(data),
        text: () => Promise.resolve(JSON.stringify(data)),
        ...options
    }),

    textResponse: (data, options = {}) => ({
        ok: true,
        status: 200,
        json: () => Promise.resolve(JSON.parse(data)),
        text: () => Promise.resolve(data),
        ...options
    }),

    mockJson(data, options={}) {
        global.fetch.mockResolvedValue(this.jsonResponse(data, options));
        return this;
    },
    mockJsonOnce(data, options={}) {
        global.fetch.mockResolvedValueOnce(this.jsonResponse(data, options));
        return this;
    },
    mockText(data, options={}) {
        global.fetch.mockResolvedValue(this.textResponse(data, options));
        return this;
    },
    mockTextOnce(data, options={}) {
        global.fetch.mockResolvedValueOnce(this.textResponse(data, options));
        return this;
    },
    mockException(exception) {
        global.fetch.mockRejectedValue(exception);
        return this;
    },
    mockExceptionOnce(exception) {
        global.fetch.mockRejectedValueOnce(exception);
        return this;
    },
    mockAbortedException() {
        global.fetch.mockRejectedValue(new DOMException('Aborted', 'AbortError'));
        return this;
    },
    mockAbortedExceptionOnce() {
        global.fetch.mockRejectedValueOnce(new DOMException('Aborted', 'AbortError'));
        return this;
    },
    mockPending() {
        global.fetch.mockImplementation(abortableFetch);
        return this;
    },
    mockPendingOnce() {
        global.fetch.mockImplementationOnce(abortableFetch);
        return this;
    },

    _implementation(response) {
        const mockedFetch = jest.fn((url, { signal } = {}) => new Promise((res,rej) => {
            const abortHandler = () => {
                clearTimeout(timer);
                rej(abortError);
            };
            if (signal instanceof AbortSignal) {
                const abortError = new DOMException('Aborted', 'AbortError');
                if (signal.aborted) {
                    rej(abortError);
                    return;
                } else {
                    signal.addEventListener('abort', abortHandler);
                }
            }        
            const responseTime = mockedFetch.mock.responseTime|0;
            const timer = setTimeout(() => {
                if (signal instanceof AbortSignal) {
                    signal.removeEventListener('abort', abortHandler);
                }
                res(response());
            }, responseTime);
        }));

        return mockedFetch;
    }
}

export default MockFetch;
