import { StorageBase } from 'storage/storage-base';
import { Features } from 'util/features';
import { UrlFormat } from 'util/formatting/url-format';

const DropboxKeys = {
    AppFolder: 'qp7ctun6qt5n9d6',
    FullDropbox: 'eor7hvv6u6oslq9'
};

const DropboxCustomErrors = {
    BadKey: 'bad-key'
};

class StorageDropbox extends StorageBase {
    name = 'dropbox';
    icon = 'dropbox';
    enabled = true;
    uipos = 20;
    backup = true;

    _toFullPath(path) {
        const rootFolder = this.appSettings.dropboxFolder;
        if (rootFolder) {
            path = UrlFormat.fixSlashes('/' + rootFolder + '/' + path);
        }
        return path;
    }

    _toRelPath(path) {
        const rootFolder = this.appSettings.dropboxFolder;
        if (rootFolder) {
            const ix = path.toLowerCase().indexOf(rootFolder.toLowerCase());
            if (ix === 0) {
                path = path.substr(rootFolder.length);
            } else if (ix === 1) {
                path = path.substr(rootFolder.length + 1);
            }
            path = UrlFormat.fixSlashes('/' + path);
        }
        return path;
    }

    _fixConfigFolder(folder) {
        folder = folder.replace(/\\/g, '/').trim();
        if (folder[0] === '/') {
            folder = folder.substr(1);
        }
        return folder;
    }

    _getKey() {
        return this.appSettings.dropboxAppKey || DropboxKeys.AppFolder;
    }

    _isValidKey() {
        const key = this._getKey();
        const isBuiltIn = key === DropboxKeys.AppFolder || key === DropboxKeys.FullDropbox;
        return key && key.indexOf(' ') < 0 && (!isBuiltIn || this._canUseBuiltInKeys());
    }

    _canUseBuiltInKeys() {
        return !Features.isSelfHosted;
    }

    _getOAuthConfig() {
        return {
            scope: '',
            url: 'https://www.dropbox.com/oauth2/authorize',
            clientId: this._getKey(),
            width: 600,
            height: 400
        };
    }

    needShowOpenConfig() {
        return !this._isValidKey();
    }

    getOpenConfig() {
        return {
            desc: 'dropboxSetupDesc',
            fields: [
                {
                    id: 'key',
                    title: 'dropboxAppKey',
                    desc: 'dropboxAppKeyDesc',
                    type: 'text',
                    required: true,
                    pattern: '\\w+'
                },
                {
                    id: 'folder',
                    title: 'dropboxFolder',
                    desc: 'dropboxFolderDesc',
                    type: 'text',
                    placeholder: 'dropboxFolderPlaceholder'
                }
            ]
        };
    }

    getSettingsConfig() {
        const fields = [];
        const appKey = this._getKey();
        const linkField = {
            id: 'link',
            title: 'dropboxLink',
            type: 'select',
            value: 'custom',
            options: { app: 'dropboxLinkApp', full: 'dropboxLinkFull', custom: 'dropboxLinkCustom' }
        };
        const keyField = {
            id: 'key',
            title: 'dropboxAppKey',
            desc: 'dropboxAppKeyDesc',
            type: 'text',
            required: true,
            pattern: '\\w+',
            value: appKey
        };
        const folderField = {
            id: 'folder',
            title: 'dropboxFolder',
            desc: 'dropboxFolderSettingsDesc',
            type: 'text',
            value: this.appSettings.dropboxFolder || ''
        };
        const canUseBuiltInKeys = this._canUseBuiltInKeys();
        if (canUseBuiltInKeys) {
            fields.push(linkField);
            if (appKey === DropboxKeys.AppFolder) {
                linkField.value = 'app';
            } else if (appKey === DropboxKeys.FullDropbox) {
                linkField.value = 'full';
                fields.push(folderField);
            } else {
                fields.push(keyField);
                fields.push(folderField);
            }
        } else {
            fields.push(keyField);
            fields.push(folderField);
        }
        return { fields };
    }

    applyConfig(config, callback) {
        if (config.key === DropboxKeys.AppFolder || config.key === DropboxKeys.FullDropbox) {
            return callback(DropboxCustomErrors.BadKey);
        }
        // TODO: try to connect using new key
        if (config.folder) {
            config.folder = this._fixConfigFolder(config.folder);
        }
        this.appSettings.set({
            dropboxAppKey: config.key,
            dropboxFolder: config.folder
        });
        callback();
    }

    applySetting(key, value) {
        switch (key) {
            case 'link':
                key = 'dropboxAppKey';
                switch (value) {
                    case 'app':
                        value = DropboxKeys.AppFolder;
                        break;
                    case 'full':
                        value = DropboxKeys.FullDropbox;
                        break;
                    case 'custom':
                        value = '(your app key)';
                        break;
                    default:
                        return;
                }
                this._oauthRevokeToken();
                break;
            case 'key':
                key = 'dropboxAppKey';
                this._oauthRevokeToken();
                break;
            case 'folder':
                key = 'dropboxFolder';
                value = this._fixConfigFolder(value);
                break;
            default:
                return;
        }
        this.appSettings[key] = value;
    }

    getPathForName(fileName) {
        return '/' + fileName + '.kdbx';
    }

    _encodeJsonHttpHeader(json) {
        return json.replace(
            /[\u007f-\uffff]/g,
            c => '\\u' + ('000' + c.charCodeAt(0).toString(16)).slice(-4)
        );
    }

    _apiCall(args) {
        this._oauthAuthorize(err => {
            if (err) {
                return args.error(err);
            }
            const host = args.host || 'api';
            let headers;
            let data = args.data;
            if (args.apiArg) {
                headers = {
                    'Dropbox-API-Arg': this._encodeJsonHttpHeader(JSON.stringify(args.apiArg))
                };
                if (args.data) {
                    headers['Content-Type'] = 'application/octet-stream';
                }
            } else if (args.data) {
                data = JSON.stringify(data);
                headers = {
                    'Content-Type': 'application/json'
                };
            }
            this._xhr({
                url: `https://${host}.dropboxapi.com/2/${args.method}`,
                method: 'POST',
                responseType: args.responseType || 'json',
                headers,
                data,
                statuses: args.statuses || undefined,
                success: args.success,
                error: (e, xhr) => {
                    let err = (xhr.response && xhr.response.error) || new Error('Network error');
                    if (err && err.path && err.path['.tag'] === 'not_found') {
                        err = new Error('File removed');
                        err.notFound = true;
                        this.logger.debug('File not found', args.method);
                    } else {
                        this.logger.error('API error', args.method, xhr.status, err);
                    }
                    err.status = xhr.status;
                    args.error(err);
                }
            });
        });
    }

    load(path, opts, callback) {
        this.logger.debug('Load', path);
        const ts = this.logger.ts();
        path = this._toFullPath(path);
        this._apiCall({
            method: 'files/download',
            host: 'content',
            apiArg: { path },
            responseType: 'arraybuffer',
            success: (response, xhr) => {
                const stat = JSON.parse(xhr.getResponseHeader('dropbox-api-result'));
                this.logger.debug('Loaded', path, stat.rev, this.logger.ts(ts));
                callback(null, response, { rev: stat.rev });
            },
            error: callback
        });
    }

    stat(path, opts, callback) {
        this.logger.debug('Stat', path);
        const ts = this.logger.ts();
        path = this._toFullPath(path);
        this._apiCall({
            method: 'files/get_metadata',
            data: { path },
            success: stat => {
                if (stat['.tag'] === 'file') {
                    stat = { rev: stat.rev };
                } else if (stat['.tag'] === 'folder') {
                    stat = { folder: true };
                }
                this.logger.debug(
                    'Stated',
                    path,
                    stat.folder ? 'folder' : stat.rev,
                    this.logger.ts(ts)
                );
                if (callback) {
                    callback(null, stat);
                }
            },
            error: callback
        });
    }

    save(path, opts, data, callback, rev) {
        this.logger.debug('Save', path, rev);
        const ts = this.logger.ts();
        path = this._toFullPath(path);
        const arg = {
            path,
            mode: rev ? { '.tag': 'update', update: rev } : { '.tag': 'overwrite' }
        };
        this._apiCall({
            method: 'files/upload',
            host: 'content',
            apiArg: arg,
            data,
            responseType: 'json',
            success: stat => {
                this.logger.debug('Saved', path, stat.rev, this.logger.ts(ts));
                callback(null, { rev: stat.rev });
            },
            error: callback
        });
    }

    list(dir, callback) {
        this.logger.debug('List');
        const ts = this.logger.ts();
        this._apiCall({
            method: 'files/list_folder',
            data: {
                path: this._toFullPath(dir || ''),
                recursive: false
            },
            success: data => {
                this.logger.debug('Listed', this.logger.ts(ts));
                const fileList = data.entries.map(f => ({
                    name: f.name,
                    path: this._toRelPath(f.path_display),
                    rev: f.rev,
                    dir: f['.tag'] !== 'file'
                }));
                callback(null, fileList);
            },
            error: callback
        });
    }

    remove(path, callback) {
        this.logger.debug('Remove', path);
        const ts = this.logger.ts();
        path = this._toFullPath(path);
        this._apiCall({
            method: 'files/delete',
            data: { path },
            success: () => {
                this.logger.debug('Removed', path, this.logger.ts(ts));
                callback();
            },
            error: callback
        });
    }

    mkdir(path, callback) {
        this.logger.debug('Make dir', path);
        const ts = this.logger.ts();
        path = this._toFullPath(path);
        this._apiCall({
            method: 'files/create_folder',
            data: { path },
            success: () => {
                this.logger.debug('Made dir', path, this.logger.ts(ts));
                callback();
            },
            error: callback
        });
    }

    setEnabled(enabled) {
        if (!enabled) {
            this._oauthRevokeToken();
        }
        super.setEnabled(enabled);
    }
}

export { StorageDropbox };
