import Backbone from 'backbone';
import { RuntimeInfo } from 'comp/app/runtime-info';
import { SettingsStore } from 'comp/settings/settings-store';
import { Plugin } from 'plugins/plugin';
import { PluginCollection } from 'plugins/plugin-collection';
import { PluginGallery } from 'plugins/plugin-gallery';
import { SignatureVerifier } from 'util/data/signature-verifier';
import { Logger } from 'util/logger';

const PluginManager = Backbone.Model.extend({
    UpdateInterval: 1000 * 60 * 60 * 24 * 7,

    defaults: {
        plugins: new PluginCollection(),
        autoUpdateAppVersion: null,
        autoUpdateDate: null
    },

    logger: new Logger('plugin-mgr'),

    init() {
        const ts = this.logger.ts();
        return SettingsStore.load('plugins').then(state => {
            if (!state) {
                return;
            }
            this.set({
                autoUpdateAppVersion: state.autoUpdateAppVersion,
                autoUpdateDate: state.autoUpdateDate
            });
            if (!state || !state.plugins || !state.plugins.length) {
                return;
            }
            return PluginGallery.getCachedGallery().then(gallery => {
                const promises = state.plugins.map(plugin => this.loadPlugin(plugin, gallery));
                return Promise.all(promises).then(loadedPlugins => {
                    const plugins = this.get('plugins');
                    plugins.add(loadedPlugins.filter(plugin => plugin));
                    this.logger.info(`Loaded ${plugins.length} plugins`, this.logger.ts(ts));
                });
            });
        });
    },

    install(url, expectedManifest, skipSignatureValidation) {
        this.trigger('change');
        return Plugin.loadFromUrl(url, expectedManifest)
            .then(plugin => {
                return this.uninstall(plugin.id).then(() => {
                    if (skipSignatureValidation) {
                        plugin.set('skipSignatureValidation', true);
                    }
                    return plugin.install(true, false).then(() => {
                        this.get('plugins').push(plugin);
                        this.trigger('change');
                        this.saveState();
                    });
                });
            })
            .catch(e => {
                this.trigger('change');
                throw e;
            });
    },

    installIfNew(url, expectedManifest, skipSignatureValidation) {
        const plugin = this.get('plugins').find({ url });
        if (plugin && plugin.get('status') !== 'invalid') {
            return Promise.resolve();
        }
        return this.install(url, expectedManifest, skipSignatureValidation);
    },

    uninstall(id) {
        const plugins = this.get('plugins');
        const plugin = plugins.get(id);
        if (!plugin) {
            return Promise.resolve();
        }
        this.trigger('change');
        return plugin.uninstall().then(() => {
            plugins.remove(id);
            this.trigger('change');
            this.saveState();
        });
    },

    disable(id) {
        const plugins = this.get('plugins');
        const plugin = plugins.get(id);
        if (!plugin || plugin.get('status') !== Plugin.STATUS_ACTIVE) {
            return Promise.resolve();
        }
        this.trigger('change');
        return plugin.disable().then(() => {
            this.trigger('change');
            this.saveState();
        });
    },

    activate(id) {
        const plugins = this.get('plugins');
        const plugin = plugins.get(id);
        if (!plugin || plugin.get('status') === Plugin.STATUS_ACTIVE) {
            return Promise.resolve();
        }
        this.trigger('change');
        return plugin.install(true, true).then(() => {
            this.trigger('change');
            this.saveState();
        });
    },

    update(id) {
        const plugins = this.get('plugins');
        const oldPlugin = plugins.get(id);
        const validStatuses = [
            Plugin.STATUS_ACTIVE,
            Plugin.STATUS_INACTIVE,
            Plugin.STATUS_NONE,
            Plugin.STATUS_ERROR,
            Plugin.STATUS_INVALID
        ];
        if (!oldPlugin || validStatuses.indexOf(oldPlugin.get('status')) < 0) {
            return Promise.reject();
        }
        const url = oldPlugin.get('url');
        this.trigger('change');
        return Plugin.loadFromUrl(url)
            .then(newPlugin => {
                return oldPlugin
                    .update(newPlugin)
                    .then(() => {
                        this.trigger('change');
                        this.saveState();
                    })
                    .catch(e => {
                        this.trigger('change');
                        throw e;
                    });
            })
            .catch(e => {
                this.trigger('change');
                throw e;
            });
    },

    setAutoUpdate(id, enabled) {
        const plugins = this.get('plugins');
        const plugin = plugins.get(id);
        if (!plugin || plugin.get('autoUpdate') === enabled) {
            return;
        }
        plugin.setAutoUpdate(enabled);
        this.trigger('change');
        this.saveState();
    },

    runAutoUpdate() {
        const queue = this.get('plugins')
            .filter(p => p.get('autoUpdate'))
            .map(p => p.id);
        if (!queue.length) {
            return Promise.resolve();
        }
        const anotherVersion = this.get('autoUpdateAppVersion') !== RuntimeInfo.version;
        const wasLongAgo =
            !this.get('autoUpdateDate') ||
            Date.now() - this.get('autoUpdateDate') > this.UpdateInterval;
        const autoUpdateRequired = anotherVersion || wasLongAgo;
        if (!autoUpdateRequired) {
            return;
        }
        this.logger.info('Auto-updating plugins', queue.join(', '));
        this.set({
            autoUpdateAppVersion: RuntimeInfo.version,
            autoUpdateDate: Date.now()
        });
        this.saveState();
        const updateNext = () => {
            const pluginId = queue.shift();
            if (pluginId) {
                return this.update(pluginId)
                    .catch(() => {})
                    .then(updateNext);
            }
        };
        return updateNext();
    },

    loadPlugin(desc, gallery) {
        const plugin = new Plugin({
            manifest: desc.manifest,
            url: desc.url,
            autoUpdate: desc.autoUpdate
        });
        let enabled = desc.enabled;
        if (enabled) {
            const galleryPlugin = gallery
                ? gallery.plugins.find(pl => pl.manifest.name === desc.manifest.name)
                : null;
            const expectedPublicKey = galleryPlugin
                ? galleryPlugin.manifest.publicKey
                : SignatureVerifier.getPublicKey();
            enabled = desc.manifest.publicKey === expectedPublicKey;
        }
        return plugin
            .install(enabled, true)
            .then(() => plugin)
            .catch(() => plugin);
    },

    saveState() {
        SettingsStore.save('plugins', {
            autoUpdateAppVersion: this.get('autoUpdateAppVersion'),
            autoUpdateDate: this.get('autoUpdateDate'),
            plugins: this.get('plugins').map(plugin => ({
                manifest: plugin.get('manifest'),
                url: plugin.get('url'),
                enabled: plugin.get('status') === 'active',
                autoUpdate: plugin.get('autoUpdate')
            }))
        });
    },

    getStatus(id) {
        const plugin = this.get('plugins').get(id);
        return plugin ? plugin.get('status') : '';
    },

    getPlugin(id) {
        return this.get('plugins').get(id);
    }
});

PluginManager.instance = new PluginManager();

export { PluginManager };
