import ng from 'core/services/ng';
import {appConfig} from '../../appConfig';

interface IHttpRequestOptions {
    method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
    url: string; // absolute url
    payload?: {};
    headers?: {[key: string]: any};
    urlParams?: {[key: string]: any};

    abortSignal?: AbortSignal;
}

interface IHttpRequestOptionsLocal extends Omit<IHttpRequestOptions, 'url'> {
    path: string; // relative to application server
}

interface IHttpRequestJsonOptionsLocal extends IHttpRequestOptionsLocal {
    // JSON not available with DELETE method
    method: 'GET' | 'POST' | 'PATCH' | 'PUT';
}

interface IHttpLocalApiErrorResponse {
    _error: {
        code: number;
        message: string;
    };
    _issues: {[key: string]: any};
    _status: 'ERR' | string;
}

export function isHttpApiError(x): x is IHttpLocalApiErrorResponse {
    return typeof x === 'object' && x['_status'] === 'ERR';
}

function httpRequestBase(options: IHttpRequestOptions): Promise<Response> {
    const {method, url, payload, headers, abortSignal} = options;

    const _url = new URL(url);

    if (options.urlParams != null) {
        Object.keys(options.urlParams).forEach((key) => {
            _url.searchParams.append(key, options.urlParams[key]);
        });
    }

    return fetch(_url.toString(), {
        method,
        headers: headers || {},
        mode: 'cors',
        body: JSON.stringify(payload), // works when `payload` is `undefined`
        signal: abortSignal,
    });
}

export function httpRequestVoidLocal(options: IHttpRequestOptionsLocal): Promise<void> {
    return ng.getService('session')
        .then((session) => {
            return httpRequestBase({
                ...options,
                url: appConfig.server.url + options.path,
                urlParams: options.urlParams,
                headers: {
                    ...(options.headers || {}),
                    'Authorization': session.token,
                },
            }).then((res) => {
                if (res.ok) {
                    return Promise.resolve();
                } else {
                    // Attempt to convert error response to JSON,
                    // otherwise return body as text as returned from the server
                    return res.text()
                        .then((bodyText) => {
                            try {
                                return Promise.reject(JSON.parse(bodyText));
                            } catch (_err) {
                                return Promise.reject(bodyText);
                            }
                        });
                }
            });
        });
}

export function httpRequestJsonLocal<T>(options: IHttpRequestJsonOptionsLocal): Promise<T> {
    return ng.getService('session')
        .then((session) => {
            return httpRequestBase({
                ...options,
                url: appConfig.server.url + options.path,
                headers: {
                    ...(options.headers || {}),
                    'Content-Type': 'application/json',
                    'Authorization': session.token,
                },
            }).then((res) => res.json().then((json) => {
                if (res.ok) {
                    return json;
                } else {
                    return Promise.reject(json);
                }
            }));
        });
}

export function httpRequestRawLocal<T>(options: IHttpRequestOptionsLocal): Promise<Response> {
    return ng.getService('session')
        .then((session) => {
            return httpRequestBase({
                ...options,
                url: appConfig.server.url + options.path,
                headers: {
                    ...(options.headers || {}),
                    'Authorization': session.token,
                },
            }).then((res) => {
                if (res.ok) {
                    return res;
                } else {
                    return Promise.reject(res);
                }
            });
        });
}

export function uploadFileWithProgress<T>(
    endpoint: string,
    data: FormData,
    onProgress: (event: ProgressEvent) => void,
    method?: 'POST' | 'PATCH',
    etag?: string,
): Promise<T> {
    return ng.getService('session')
        .then((session) => {
            return new Promise<T>((resolve, reject) => {
                // Using `XMLHttpRequest` over `fetch` so we can get `onprogress` reporting
                const request = new XMLHttpRequest();
                const url = appConfig.server.url + endpoint;

                request.open(method ?? 'POST', url);
                request.setRequestHeader('Authorization', session.token);

                if (method === 'PATCH' && etag != null) {
                    request.setRequestHeader('If-Match', etag);
                }

                request.upload.onprogress = onProgress;

                request.onload = function() {
                    if (this.status >= 200 && this.status < 300) {
                        resolve(JSON.parse(this.responseText));
                    } else {
                        reject(JSON.parse(this.responseText));
                    }
                };

                request.onerror = function(e: ProgressEvent) {
                    reject(e);
                };

                request.send(data);
            });
        });
}
