const $      = require('jquery');
const _      = require('underscore');
const Tokens = require('./tokens');

/**
 * @param {String}  message
 * @param {*}       debug
 * @param {Number} code
 * @constructor
 */
const ApiError = function (message, debug, code) {
    let temp     = Error.call(this, message);
    temp.name    = this.name = 'ApiError';
    this.stack   = temp.stack;
    this.message = temp.message;
    this.debug   = debug;
    this.code    = code;
};

ApiError.prototype = Object.create(Error.prototype, {
    constructor: {
        value:        ApiError,
        writable:     true,
        configurable: true
    }
});

/**
 *
 * @param   {String} verb
 * @param   {String} path
 * @param   {Object} [data]
 * @param   {Object} [options]
 * @returns {Promise}
 */
function fetch(verb, path, data, options) {
    options = options || {};

    return new Promise(function (resolve, reject) {
        let api_url = '/api/';
        let url     = api_url + path;
        let token   = Tokens.getTopToken();

        if ((typeof options.contentType === 'undefined' || options.contentType.match(/json/im)) && typeof data === 'object') {
            data = JSON.stringify(data);
        }

        $.ajax({
            url:         url,
            data:        typeof data === 'object' ? JSON.stringify(data) : data,
            type:        verb,
            dataType:    'json',
            contentType: options.contentType || 'application/json; charset=UTF-8',
            processData: options.processData || true,
            crossDomain: true,
            timeout:     options.timeout ? options.timeout : 180000,
            xhrFields:   {
                withCredentials: true
            },

            beforeSend: function (xhr) {
                xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null));
            },

            success: function (data, textStatus, response) {
                let total = response.getResponseHeader('X-Dataset-Total');
                if (total !== null) {
                    resolve({
                        data:       data,
                        pagination: {
                            total:  parseInt(total, 10),
                            offset: parseInt(response.getResponseHeader('X-Dataset-Offset'), 10),
                            limit:  parseInt(response.getResponseHeader('X-Dataset-Limit'), 10)
                        }
                    });
                } else {
                    resolve(response);
                }
            },

            error: function (xhr, status, error_thrown) {
                let code = 400;

                if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') {
                    error_thrown = xhr.responseJSON.error.message;
                    code         = xhr.responseJSON.error.code || 500;
                }

                reject(new ApiError(error_thrown, xhr.responseText, code));
            }
        });
    });
}

/**
 *
 * @param {Array} expand
 * @returns {String}
 */
function makeExpansionString(expand) {
    let items = [];
    _.forEach(expand, function (exp) {
        items.push(encodeURIComponent(exp));
    });

    return items.join(',');
}

/**
 * @param   {String}   path
 * @param   {Array}    [expand]
 * @param   {String}   [query]
 * @returns {Promise}
 */
function getAllObjects(path, expand, query) {
    let params = [];

    if (typeof expand === 'object' && expand !== null && expand.length) {
        params.push('expand=' + makeExpansionString(expand));
    }

    if (typeof query === 'string') {
        params.push('query=' + query);
    }

    return fetch('get', path + (params.length ? '?' + params.join('&') : ''));
}

function FileUpload(path, fd) {
    return new Promise((resolve, reject) => {
        let xhr   = new XMLHttpRequest();
        let token = Tokens.getTopToken();

        xhr.open('POST', '/api/' + path);
        xhr.overrideMimeType('text/plain');
        xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null));
        xhr.send(fd);

        xhr.onreadystatechange = function () {
            if (this.readyState === XMLHttpRequest.DONE) {
                if (xhr.status !== 200 && xhr.status !== 201) {
                    try {
                        reject(new Error('Upload failed: ' + JSON.parse(xhr.responseText).error.message));
                    } catch (err) {
                        reject(new Error('Upload failed: ' + xhr.status));
                    }  
                } else {
                    resolve(xhr.responseText);
                }
            }
        };
    });
}

//ref : https://codepen.io/chrisdpratt/pen/RKxJNo
function DownloadFile(verb, path, filename) {
    return new Promise(function (resolve, reject) {
        let api_url = '/api/';
        let url = api_url + path;
        let token = Tokens.getTopToken();

        $.ajax({
            url: url,
            type: verb,
            crossDomain: true,
            xhrFields: {
                withCredentials: true,
                responseType: 'blob'
            },

            beforeSend: function (xhr) {
                xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null));
            },

            success: function (data) {
                var a = document.createElement('a');
                var url = window.URL.createObjectURL(data);
                a.href = url;
                a.download = filename;
                document.body.append(a);
                a.click();
                a.remove();
                window.URL.revokeObjectURL(url);
            },

            error: function (xhr, status, error_thrown) {
                let code = 400;

                if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') {
                    error_thrown = xhr.responseJSON.error.message;
                    code = xhr.responseJSON.error.code || 500;
                }

                reject(new ApiError(error_thrown, xhr.responseText, code));
            }
        });
    });
}

module.exports = {
    status: function () {
        return fetch('get', '');
    },

    Tokens: {

        /**
         * @param   {String}  identity
         * @param   {String}  secret
         * @param   {Boolean} [wipe]       Will wipe the stack before adding to it again if login was successful
         * @returns {Promise}
         */
        login: function (identity, secret, wipe) {
            return fetch('post', 'tokens', {identity: identity, secret: secret})
                .then(response => {
                    if (response.token) {
                        if (wipe) {
                            Tokens.clearTokens();
                        }

                        // Set storage token
                        Tokens.addToken(response.token);
                        return response.token;
                    } else {
                        Tokens.clearTokens();
                        throw(new Error('No token returned'));
                    }
                });
        },

        /**
         * @returns {Promise}
         */
        refresh: function () {
            return fetch('get', 'tokens')
                .then(response => {
                    if (response.token) {
                        Tokens.setCurrentToken(response.token);
                        return response.token;
                    } else {
                        Tokens.clearTokens();
                        throw(new Error('No token returned'));
                    }
                });
        }
    },

    Users: {

        /**
         * @param   {Number|String}  user_id
         * @param   {Array}           [expand]
         * @returns {Promise}
         */
        getById: function (user_id, expand) {
            return fetch('get', 'users/' + user_id + (typeof expand === 'object' && expand.length ? '?expand=' + makeExpansionString(expand) : ''));
        },

        /**
         * @param   {Array}    [expand]
         * @param   {String}   [query]
         * @returns {Promise}
         */
        getAll: function (expand, query) {
            return getAllObjects('users', expand, query);
        },

        /**
         * @param   {Object}  data
         * @returns {Promise}
         */
        create: function (data) {
            return fetch('post', 'users', data);
        },

        /**
         * @param   {Object}   data
         * @param   {Number}   data.id
         * @returns {Promise}
         */
        update: function (data) {
            let id = data.id;
            delete data.id;
            return fetch('put', 'users/' + id, data);
        },

        /**
         * @param   {Number}  id
         * @returns {Promise}
         */
        delete: function (id) {
            return fetch('delete', 'users/' + id);
        },

        /**
         *
         * @param   {Number}   id
         * @param   {Object}   auth
         * @returns {Promise}
         */
        setPassword: function (id, auth) {
            return fetch('put', 'users/' + id + '/auth', auth);
        },

        /**
         * @param   {Number}  id
         * @returns {Promise}
         */
        loginAs: function (id) {
            return fetch('post', 'users/' + id + '/login');
        },

        /**
         *
         * @param   {Number}   id
         * @param   {Object}   perms
         * @returns {Promise}
         */
        setPermissions: function (id, perms) {
            return fetch('put', 'users/' + id + '/permissions', perms);
        }
    },

    Nginx: {

        ProxyHosts: {
            /**
             * @param   {Array}    [expand]
             * @param   {String}   [query]
             * @returns {Promise}
             */
            getAll: function (expand, query) {
                return getAllObjects('nginx/proxy-hosts', expand, query);
            },

            /**
             * @param {Object}  data
             */
            create: function (data) {
                return fetch('post', 'nginx/proxy-hosts', data);
            },

            /**
             * @param   {Object}   data
             * @param   {Number}  data.id
             * @returns {Promise}
             */
            update: function (data) {
                let id = data.id;
                delete data.id;
                return fetch('put', 'nginx/proxy-hosts/' + id, data);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            delete: function (id) {
                return fetch('delete', 'nginx/proxy-hosts/' + id);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            get: function (id) {
                return fetch('get', 'nginx/proxy-hosts/' + id);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            enable: function (id) {
                return fetch('post', 'nginx/proxy-hosts/' + id + '/enable');
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            disable: function (id) {
                return fetch('post', 'nginx/proxy-hosts/' + id + '/disable');
            }
        },

        RedirectionHosts: {
            /**
             * @param   {Array}    [expand]
             * @param   {String}   [query]
             * @returns {Promise}
             */
            getAll: function (expand, query) {
                return getAllObjects('nginx/redirection-hosts', expand, query);
            },

            /**
             * @param {Object}  data
             */
            create: function (data) {
                return fetch('post', 'nginx/redirection-hosts', data);
            },

            /**
             * @param   {Object}   data
             * @param   {Number}   data.id
             * @returns {Promise}
             */
            update: function (data) {
                let id = data.id;
                delete data.id;
                return fetch('put', 'nginx/redirection-hosts/' + id, data);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            delete: function (id) {
                return fetch('delete', 'nginx/redirection-hosts/' + id);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            get: function (id) {
                return fetch('get', 'nginx/redirection-hosts/' + id);
            },

            /**
             * @param  {Number}   id
             * @param  {FormData} form_data
             * @params {Promise}
             */
            setCerts: function (id, form_data) {
                return FileUpload('nginx/redirection-hosts/' + id + '/certificates', form_data);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            enable: function (id) {
                return fetch('post', 'nginx/redirection-hosts/' + id + '/enable');
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            disable: function (id) {
                return fetch('post', 'nginx/redirection-hosts/' + id + '/disable');
            }
        },

        Streams: {
            /**
             * @param   {Array}    [expand]
             * @param   {String}   [query]
             * @returns {Promise}
             */
            getAll: function (expand, query) {
                return getAllObjects('nginx/streams', expand, query);
            },

            /**
             * @param {Object}  data
             */
            create: function (data) {
                return fetch('post', 'nginx/streams', data);
            },

            /**
             * @param   {Object}   data
             * @param   {Number}   data.id
             * @returns {Promise}
             */
            update: function (data) {
                let id = data.id;
                delete data.id;
                return fetch('put', 'nginx/streams/' + id, data);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            delete: function (id) {
                return fetch('delete', 'nginx/streams/' + id);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            get: function (id) {
                return fetch('get', 'nginx/streams/' + id);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            enable: function (id) {
                return fetch('post', 'nginx/streams/' + id + '/enable');
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            disable: function (id) {
                return fetch('post', 'nginx/streams/' + id + '/disable');
            }
        },

        DeadHosts: {
            /**
             * @param   {Array}    [expand]
             * @param   {String}   [query]
             * @returns {Promise}
             */
            getAll: function (expand, query) {
                return getAllObjects('nginx/dead-hosts', expand, query);
            },

            /**
             * @param {Object}  data
             */
            create: function (data) {
                return fetch('post', 'nginx/dead-hosts', data);
            },

            /**
             * @param   {Object}   data
             * @param   {Number}   data.id
             * @returns {Promise}
             */
            update: function (data) {
                let id = data.id;
                delete data.id;
                return fetch('put', 'nginx/dead-hosts/' + id, data);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            delete: function (id) {
                return fetch('delete', 'nginx/dead-hosts/' + id);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            get: function (id) {
                return fetch('get', 'nginx/dead-hosts/' + id);
            },

            /**
             * @param  {Number}   id
             * @param  {FormData} form_data
             * @params {Promise}
             */
            setCerts: function (id, form_data) {
                return FileUpload('nginx/dead-hosts/' + id + '/certificates', form_data);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            enable: function (id) {
                return fetch('post', 'nginx/dead-hosts/' + id + '/enable');
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            disable: function (id) {
                return fetch('post', 'nginx/dead-hosts/' + id + '/disable');
            }
        },

        AccessLists: {
            /**
             * @param   {Array}    [expand]
             * @param   {String}   [query]
             * @returns {Promise}
             */
            getAll: function (expand, query) {
                return getAllObjects('nginx/access-lists', expand, query);
            },

            /**
             * @param {Object}  data
             */
            create: function (data) {
                return fetch('post', 'nginx/access-lists', data);
            },

            /**
             * @param   {Object}   data
             * @param   {Number}   data.id
             * @returns {Promise}
             */
            update: function (data) {
                let id = data.id;
                delete data.id;
                return fetch('put', 'nginx/access-lists/' + id, data);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            delete: function (id) {
                return fetch('delete', 'nginx/access-lists/' + id);
            }
        },

        Certificates: {
            /**
             * @param   {Array}    [expand]
             * @param   {String}   [query]
             * @returns {Promise}
             */
            getAll: function (expand, query) {
                return getAllObjects('nginx/certificates', expand, query);
            },

            /**
             * @param {Object}  data
             */
            create: function (data) {

                const timeout = 180000 + (data && data.meta && data.meta.propagation_seconds ? Number(data.meta.propagation_seconds) * 1000 : 0);
                return fetch('post', 'nginx/certificates', data, {timeout});
            },

            /**
             * @param   {Object}   data
             * @param   {Number}   data.id
             * @returns {Promise}
             */
            update: function (data) {
                let id = data.id;
                delete data.id;
                return fetch('put', 'nginx/certificates/' + id, data);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            delete: function (id) {
                return fetch('delete', 'nginx/certificates/' + id);
            },

            /**
             * @param  {Number}  id
             * @param  {FormData} form_data
             * @params {Promise}
             */
            upload: function (id, form_data) {
                return FileUpload('nginx/certificates/' + id + '/upload', form_data);
            },

            /**
             * @param  {FormData} form_data
             * @params {Promise}
             */
            validate: function (form_data) {
                return FileUpload('nginx/certificates/validate', form_data);
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            renew: function (id, timeout = 180000) {
                return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout});
            },

            /**
             * @param  {Number}  id
             * @returns {Promise}
             */
            testHttpChallenge: function (domains) {
                return fetch('get', 'nginx/certificates/test-http?' + new URLSearchParams({
                    domains: JSON.stringify(domains),
                }));
            },

            /**
             * @param   {Number}  id
             * @returns {Promise}
             */
            download: function (id) {
                return DownloadFile('get', "nginx/certificates/" + id + "/download", "certificate.zip")
            }
        }
    },

    AuditLog: {
        /**
         * @param   {Array}    [expand]
         * @param   {String}   [query]
         * @returns {Promise}
         */
        getAll: function (expand, query) {
            return getAllObjects('audit-log', expand, query);
        }
    },

    Reports: {

        /**
         * @returns {Promise}
         */
        getHostStats: function () {
            return fetch('get', 'reports/hosts');
        }
    },

    Settings: {

        /**
         * @param   {String}  setting_id
         * @returns {Promise}
         */
        getById: function (setting_id) {
            return fetch('get', 'settings/' + setting_id);
        },

        /**
         * @returns {Promise}
         */
        getAll: function () {
            return getAllObjects('settings');
        },

        /**
         * @param   {Object}   data
         * @param   {Number}   data.id
         * @returns {Promise}
         */
        update: function (data) {
            let id = data.id;
            delete data.id;
            return fetch('put', 'settings/' + id, data);
        }
    }
};