var Settings,
    ghostBookshelf = require('./base'),
    uuid           = require('node-uuid'),
    _              = require('lodash'),
    errors         = require('../errors'),
    Promise        = require('bluebird'),
    validation     = require('../data/validation'),
    events         = require('../events'),
    internal       = {context: {internal: true}},

    defaultSettings;

// For neatness, the defaults file is split into categories.
// It's much easier for us to work with it as a single level
// instead of iterating those categories every time
function parseDefaultSettings() {
    var defaultSettingsInCategories = require('../data/default-settings.json'),
        defaultSettingsFlattened = {};

    _.each(defaultSettingsInCategories, function each(settings, categoryName) {
        _.each(settings, function each(setting, settingName) {
            setting.type = categoryName;
            setting.key = settingName;

            defaultSettingsFlattened[settingName] = setting;
        });
    });

    return defaultSettingsFlattened;
}

function getDefaultSettings() {
    if (!defaultSettings) {
        defaultSettings = parseDefaultSettings();
    }

    return defaultSettings;
}

// Each setting is saved as a separate row in the database,
// but the overlying API treats them as a single key:value mapping
Settings = ghostBookshelf.Model.extend({

    tableName: 'settings',

    defaults: function defaults() {
        return {
            uuid: uuid.v4(),
            type: 'core'
        };
    },

    emitChange: function emitChange(event) {
        events.emit('settings' + '.' + event, this);
    },

    initialize: function initialize() {
        ghostBookshelf.Model.prototype.initialize.apply(this, arguments);

        this.on('created', function (model) {
            model.emitChange('added');
            model.emitChange(model.attributes.key + '.' + 'added');
        });
        this.on('updated', function (model) {
            model.emitChange('edited');
            model.emitChange(model.attributes.key + '.' + 'edited');
        });
        this.on('destroyed', function (model) {
            model.emitChange('deleted');
            model.emitChange(model.attributes.key + '.' + 'deleted');
        });
    },

    validate: function validate() {
        var self = this,
            setting = this.toJSON();

        return validation.validateSchema(self.tableName, setting).then(function then() {
            return validation.validateSettings(getDefaultSettings(), self);
        }).then(function () {
            var themeName = setting.value || '';

            if (setting.key !== 'activeTheme') {
                return;
            }

            return validation.validateActiveTheme(themeName);
        });
    },

    saving: function saving() {
        // disabling sanitization until we can implement a better version
        // All blog setting keys that need their values to be escaped.
        // if (this.get('type') === 'blog' && _.contains(['title', 'description', 'email'], this.get('key'))) {
        //    this.set('value', this.sanitize('value'));
        // }

        return ghostBookshelf.Model.prototype.saving.apply(this, arguments);
    }

}, {
    findOne: function (options) {
        // Allow for just passing the key instead of attributes
        if (!_.isObject(options)) {
            options = {key: options};
        }
        return Promise.resolve(ghostBookshelf.Model.findOne.call(this, options));
    },

    edit: function (data, options) {
        var self = this;
        options = this.filterOptions(options, 'edit');

        if (!Array.isArray(data)) {
            data = [data];
        }

        return Promise.map(data, function (item) {
            // Accept an array of models as input
            if (item.toJSON) { item = item.toJSON(); }
            if (!(_.isString(item.key) && item.key.length > 0)) {
                return Promise.reject(new errors.ValidationError('Value in [settings.key] cannot be blank.'));
            }

            item = self.filterData(item);

            return Settings.forge({key: item.key}).fetch(options).then(function then(setting) {
                var saveData = {};

                if (setting) {
                    if (item.hasOwnProperty('value')) {
                        saveData.value = item.value;
                    }
                    // Internal context can overwrite type (for fixture migrations)
                    if (options.context.internal && item.hasOwnProperty('type')) {
                        saveData.type = item.type;
                    }
                    return setting.save(saveData, options);
                }

                return Promise.reject(new errors.NotFoundError('Unable to find setting to update: ' + item.key));
            }, errors.logAndThrowError);
        });
    },

    populateDefault: function (key) {
        if (!getDefaultSettings()[key]) {
            return Promise.reject(new errors.NotFoundError('Unable to find default setting: ' + key));
        }

        return this.findOne({key: key}).then(function then(foundSetting) {
            if (foundSetting) {
                return foundSetting;
            }

            var defaultSetting = _.clone(getDefaultSettings()[key]);
            defaultSetting.value = defaultSetting.defaultValue;

            return Settings.forge(defaultSetting).save(null, internal);
        });
    },

    populateDefaults: function populateDefaults() {
        return this.findAll().then(function then(allSettings) {
            var usedKeys = allSettings.models.map(function mapper(setting) { return setting.get('key'); }),
                insertOperations = [];

            _.each(getDefaultSettings(), function each(defaultSetting, defaultSettingKey) {
                var isMissingFromDB = usedKeys.indexOf(defaultSettingKey) === -1;
                // Temporary code to deal with old databases with currentVersion settings
                if (defaultSettingKey === 'databaseVersion' && usedKeys.indexOf('currentVersion') !== -1) {
                    isMissingFromDB = false;
                }
                if (isMissingFromDB) {
                    defaultSetting.value = defaultSetting.defaultValue;
                    insertOperations.push(Settings.forge(defaultSetting).save(null, internal));
                }
            });

            return Promise.all(insertOperations);
        });
    }

});

module.exports = {
    Settings: ghostBookshelf.model('Settings', Settings)
};
