const _ = require('lodash');
const Promise = require('bluebird');
const should = require('should');
const rewire = require('rewire');
const sinon = require('sinon');
const moment = require('moment');
const uuid = require('uuid');
const testUtils = require('../../utils');
const configUtils = require('../../utils/configUtils');
const packageInfo = require('../../../package.json');
const api = require('../../../core/server/api').v2;

let updateCheck = rewire('../../../core/server/update-check');
let NotificationsAPI = rewire('../../../core/server/api/v2/notifications');

describe('Update Check', function () {
    beforeEach(function () {
        updateCheck = rewire('../../../core/server/update-check');
        NotificationsAPI = rewire('../../../core/server/api/v2/notifications');
    });

    afterEach(function () {
        sinon.restore();
        configUtils.restore();
    });

    after(testUtils.teardownDb);

    describe('fn: updateCheck', function () {
        let updateCheckRequestSpy;
        let updateCheckResponseSpy;
        let updateCheckErrorSpy;

        beforeEach(testUtils.teardownDb);
        beforeEach(testUtils.setup('roles', 'owner'));

        beforeEach(function () {
            updateCheckRequestSpy = sinon.stub().returns(Promise.resolve());
            updateCheckResponseSpy = sinon.stub().returns(Promise.resolve());
            updateCheckErrorSpy = sinon.stub();

            updateCheck.__set__('updateCheckRequest', updateCheckRequestSpy);
            updateCheck.__set__('updateCheckResponse', updateCheckResponseSpy);
            updateCheck.__set__('updateCheckError', updateCheckErrorSpy);
            updateCheck.__set__('allowedCheckEnvironments', ['development', 'production', 'testing', 'testing-mysql']);
        });

        it('update check was never executed', function (done) {
            const readStub = sinon.stub().resolves({
                settings: [{
                    value: null
                }]
            });
            sinon.stub(api, 'settings').get(() => ({
                read: readStub
            }));

            updateCheck()
                .then(function () {
                    updateCheckRequestSpy.calledOnce.should.eql(true);
                    updateCheckResponseSpy.calledOnce.should.eql(true);
                    updateCheckErrorSpy.called.should.eql(false);
                    done();
                })
                .catch(done);
        });

        it('update check won\'t happen if it\'s too early', function (done) {
            const readStub = sinon.stub().resolves({
                settings: [{
                    value: moment().add('10', 'minutes').unix()
                }]
            });
            sinon.stub(api, 'settings').get(() => ({
                read: readStub
            }));

            updateCheck()
                .then(function () {
                    updateCheckRequestSpy.calledOnce.should.eql(false);
                    updateCheckResponseSpy.calledOnce.should.eql(false);
                    updateCheckErrorSpy.called.should.eql(false);
                    done();
                })
                .catch(done);
        });

        it('update check will happen if it\'s time to check', function (done) {
            const readStub = sinon.stub().resolves({
                settings: [{
                    value: moment().subtract('10', 'minutes').unix()
                }]
            });
            sinon.stub(api, 'settings').get(() => ({
                read: readStub
            }));

            updateCheck()
                .then(function () {
                    updateCheckRequestSpy.calledOnce.should.eql(true);
                    updateCheckResponseSpy.calledOnce.should.eql(true);
                    updateCheckErrorSpy.called.should.eql(false);
                    done();
                })
                .catch(done);
        });
    });

    describe('fn: updateCheckData', function () {
        let environmentsOrig;

        before(function () {
            configUtils.set('privacy:useUpdateCheck', true);
        });

        after(function () {
            configUtils.restore();
        });

        beforeEach(testUtils.teardownDb);
        beforeEach(testUtils.setup('roles', 'owner', 'settings', 'posts', 'perms:setting', 'perms:user', 'perms:init'));

        it('should report the correct data', function (done) {
            const updateCheckData = updateCheck.__get__('updateCheckData');

            updateCheckData().then(function (data) {
                should.exist(data);
                data.ghost_version.should.equal(packageInfo.version);
                data.node_version.should.equal(process.versions.node);
                data.env.should.equal(process.env.NODE_ENV);
                data.database_type.should.match(/sqlite3|mysql/);
                data.blog_id.should.be.a.String();
                data.blog_id.should.not.be.empty();
                data.theme.should.be.equal('casper');
                data.blog_created_at.should.be.a.Number();
                data.user_count.should.be.above(0);
                data.post_count.should.be.above(0);
                data.npm_version.should.be.a.String();
                data.npm_version.should.not.be.empty();

                done();
            }).catch(done);
        });
    });

    describe('fn: createCustomNotification', function () {
        let currentVersionOrig;

        before(function () {
            currentVersionOrig = updateCheck.__get__('ghostVersion.original');
            updateCheck.__set__('ghostVersion.original', '0.9.0');
        });

        after(function () {
            updateCheck.__set__('ghostVersion.original', currentVersionOrig);
        });

        beforeEach(testUtils.teardownDb);
        beforeEach(testUtils.setup('settings', 'roles', 'owner', 'perms:setting', 'perms:notification', 'perms:user', 'perms:init'));

        beforeEach(function () {
            return api.notifications.destroyAll(testUtils.context.internal);
        });

        it('should create a release notification for target version', function (done) {
            const createCustomNotification = updateCheck.__get__('createCustomNotification');

            const notification = {
                id: 1,
                custom: 0,
                messages: [{
                    id: uuid.v4(),
                    version: '0.9.x',
                    content: '<p>Hey there! This is for 0.9.0 version</p>',
                    dismissible: true,
                    top: true
                }]
            };

            NotificationsAPI.__set__('ghostVersion.full', '0.8.1');

            createCustomNotification(notification).then(function () {
                return api.notifications.browse(testUtils.context.internal);
            }).then(function (results) {
                should.exist(results);
                should.exist(results.notifications);
                results.notifications.length.should.eql(1);

                const targetNotification = _.find(results.notifications, {id: notification.messages[0].id});
                should.exist(targetNotification);

                targetNotification.dismissible.should.eql(notification.messages[0].dismissible);
                targetNotification.id.should.eql(notification.messages[0].id);
                targetNotification.top.should.eql(notification.messages[0].top);
                targetNotification.type.should.eql('info');
                targetNotification.message.should.eql(notification.messages[0].content);
                done();
            }).catch(done);
        });

        it('release notification version format is wrong', function (done) {
            const createCustomNotification = updateCheck.__get__('createCustomNotification');

            const notification = {
                id: 1,
                custom: 0,
                messages: [{
                    id: uuid.v4(),
                    version: '0.9.x',
                    content: '<p>Hey there! This is for 0.9 version</p>',
                    dismissible: true,
                    top: true
                }]
            };

            NotificationsAPI.__set__('ghostVersion.full', '0.8.1');

            createCustomNotification(notification).then(function () {
                return api.notifications.browse(testUtils.context.internal);
            }).then(function (results) {
                should.exist(results);
                should.exist(results.notifications);
                results.notifications.length.should.eql(0);
                done();
            }).catch(done);
        });

        it('blog version format is wrong', function (done) {
            const createCustomNotification = updateCheck.__get__('createCustomNotification');

            const notification = {
                id: 1,
                custom: 0,
                messages: [{
                    id: uuid.v4(),
                    version: '0.9.x',
                    content: '<p>Hey there! This is for 0.9.0 version</p>',
                    dismissible: true,
                    top: true
                }]
            };

            NotificationsAPI.__set__('ghostVersion.full', '0.8');

            createCustomNotification(notification).then(function () {
                return api.notifications.browse(testUtils.context.internal);
            }).then(function (results) {
                should.exist(results);
                should.exist(results.notifications);
                results.notifications.length.should.eql(0);
                done();
            }).catch(done);
        });

        it('should create a custom notification', function (done) {
            const createCustomNotification = updateCheck.__get__('createCustomNotification');

            const notification = {
                id: 1,
                custom: 1,
                messages: [{
                    id: uuid.v4(),
                    version: 'custom1',
                    content: '<p>How about migrating your blog?</p>',
                    dismissible: false,
                    top: true,
                    type: 'warn'
                }]
            };

            createCustomNotification(notification).then(function () {
                return api.notifications.browse(testUtils.context.internal);
            }).then(function (results) {
                should.exist(results);
                should.exist(results.notifications);
                results.notifications.length.should.eql(1);

                const targetNotification = _.find(results.notifications, {id: notification.messages[0].id});
                should.exist(targetNotification);
                targetNotification.dismissible.should.eql(notification.messages[0].dismissible);
                targetNotification.top.should.eql(notification.messages[0].top);
                targetNotification.type.should.eql(notification.messages[0].type);
                done();
            }).catch(done);
        });

        it('should not add duplicates', function (done) {
            const createCustomNotification = updateCheck.__get__('createCustomNotification');

            const notification = {
                id: 1,
                custom: 1,
                messages: [{
                    id: uuid.v4(),
                    version: 'custom1',
                    content: '<p>How about migrating your blog?</p>',
                    dismissible: false,
                    top: true,
                    type: 'warn'
                }]
            };

            createCustomNotification(notification)
                .then(function () {
                    return api.notifications.browse(testUtils.context.internal);
                })
                .then(function (results) {
                    should.exist(results);
                    should.exist(results.notifications);
                    results.notifications.length.should.eql(1);
                })
                .then(function () {
                    return createCustomNotification(notification);
                })
                .then(function () {
                    return api.notifications.browse(testUtils.context.internal);
                })
                .then(function (results) {
                    should.exist(results);
                    should.exist(results.notifications);
                    results.notifications.length.should.eql(1);
                    done();
                })
                .catch(done);
        });
    });

    describe('fn: updateCheckResponse', function () {
        beforeEach(testUtils.teardownDb);
        beforeEach(testUtils.setup('roles', 'settings', 'perms:setting', 'perms:init'));

        it('receives a notifications with messages', function (done) {
            const updateCheckResponse = updateCheck.__get__('updateCheckResponse');
            const createNotificationSpy = sinon.spy();

            const message = {
                id: uuid.v4(),
                version: '^0.11.11',
                content: 'Test',
                dismissible: true,
                top: true
            };

            updateCheck.__set__('createCustomNotification', createNotificationSpy);

            updateCheckResponse({version: '0.11.12', messages: [message]})
                .then(function () {
                    createNotificationSpy.callCount.should.eql(1);
                    done();
                })
                .catch(done);
        });

        it('receives multiple notifications', function (done) {
            const updateCheckResponse = updateCheck.__get__('updateCheckResponse');
            const createNotificationSpy = sinon.spy();

            const message1 = {
                id: uuid.v4(),
                version: '^0.11.11',
                content: 'Test1',
                dismissible: true,
                top: true
            };

            const message2 = {
                id: uuid.v4(),
                version: '^0',
                content: 'Test2',
                dismissible: true,
                top: false
            };

            const notifications = [
                {version: '0.11.12', messages: [message1]},
                {version: 'custom1', messages: [message2]}
            ];

            updateCheck.__set__('createCustomNotification', createNotificationSpy);

            updateCheckResponse(notifications)
                .then(function () {
                    createNotificationSpy.callCount.should.eql(2);
                    done();
                })
                .catch(done);
        });

        it('ignores some custom notifications which are not marked as group', function (done) {
            const updateCheckResponse = updateCheck.__get__('updateCheckResponse');
            const createNotificationSpy = sinon.spy();

            const message1 = {
                id: uuid.v4(),
                version: '^0.11.11',
                content: 'Test1',
                dismissible: true,
                top: true
            };

            const message2 = {
                id: uuid.v4(),
                version: '^0',
                content: 'Test2',
                dismissible: true,
                top: false
            };

            const message3 = {
                id: uuid.v4(),
                version: '^0',
                content: 'Test2',
                dismissible: true,
                top: false
            };

            const notifications = [
                {version: '0.11.12', messages: [message1]},
                {version: 'all1', messages: [message2], custom: 1},
                {version: 'migration1', messages: [message3], custom: 1}
            ];

            updateCheck.__set__('createCustomNotification', createNotificationSpy);

            updateCheckResponse(notifications)
                .then(function () {
                    createNotificationSpy.callCount.should.eql(2);
                    done();
                })
                .catch(done);
        });

        it('group matches', function (done) {
            const updateCheckResponse = updateCheck.__get__('updateCheckResponse');
            const createNotificationSpy = sinon.spy();

            const message1 = {
                id: uuid.v4(),
                version: '^0.11.11',
                content: 'Test1',
                dismissible: true,
                top: true
            };

            const message2 = {
                id: uuid.v4(),
                version: '^0',
                content: 'Test2',
                dismissible: true,
                top: false
            };

            const message3 = {
                id: uuid.v4(),
                version: '^0',
                content: 'Test2',
                dismissible: true,
                top: false
            };

            const notifications = [
                {version: '0.11.12', messages: [message1], custom: 0},
                {version: 'all1', messages: [message2], custom: 1},
                {version: 'migration1', messages: [message3], custom: 1}
            ];

            updateCheck.__set__('createCustomNotification', createNotificationSpy);

            configUtils.set({notificationGroups: ['migration']});

            updateCheckResponse(notifications)
                .then(function () {
                    createNotificationSpy.callCount.should.eql(3);
                    done();
                })
                .catch(done);
        });

        it('single custom notification received, group matches', function (done) {
            const updateCheckResponse = updateCheck.__get__('updateCheckResponse');
            const createNotificationSpy = sinon.spy();

            const message1 = {
                id: uuid.v4(),
                version: '^0.11.11',
                content: 'Custom',
                dismissible: true,
                top: true
            };

            const notifications = [
                {version: 'something', messages: [message1], custom: 1}
            ];

            updateCheck.__set__('createCustomNotification', createNotificationSpy);

            configUtils.set({notificationGroups: ['something']});

            updateCheckResponse(notifications)
                .then(function () {
                    createNotificationSpy.callCount.should.eql(1);
                    done();
                })
                .catch(done);
        });

        it('single custom notification received, group does not match', function (done) {
            const updateCheckResponse = updateCheck.__get__('updateCheckResponse');
            const createNotificationSpy = sinon.spy();

            const message1 = {
                id: uuid.v4(),
                version: '^0.11.11',
                content: 'Custom',
                dismissible: true,
                top: true
            };

            const notifications = [
                {version: 'something', messages: [message1], custom: 1}
            ];

            updateCheck.__set__('createCustomNotification', createNotificationSpy);

            configUtils.set({notificationGroups: ['migration']});

            updateCheckResponse(notifications)
                .then(function () {
                    createNotificationSpy.callCount.should.eql(0);
                    done();
                })
                .catch(done);
        });
    });

    describe('fn: updateCheckRequest', function () {
        beforeEach(function () {
            configUtils.set('privacy:useUpdateCheck', true);
        });

        afterEach(function () {
            configUtils.restore();
        });

        it('[default]', function () {
            const updateCheckRequest = updateCheck.__get__('updateCheckRequest');
            const updateCheckDataSpy = sinon.stub();
            let hostname;
            let reqObj;

            const data = {
                ghost_version: '0.11.11',
                blog_id: 'something',
                npm_version: 'something'
            };

            updateCheck.__set__('request', function (_hostname, _reqObj) {
                hostname = _hostname;
                reqObj = _reqObj;

                return Promise.resolve({
                    statusCode: 200,
                    body: {version: 'something'}
                });
            });

            updateCheck.__set__('updateCheckData', updateCheckDataSpy);

            updateCheckDataSpy.returns(Promise.resolve(data));

            return updateCheckRequest()
                .then(function () {
                    hostname.should.eql('https://updates.ghost.org');
                    should.exist(reqObj.headers['Content-Length']);
                    reqObj.body.should.eql(data);
                    reqObj.json.should.eql(true);
                });
        });

        it('privacy flag is used', function () {
            const updateCheckRequest = updateCheck.__get__('updateCheckRequest');
            const updateCheckDataSpy = sinon.stub();
            let reqObj;
            let hostname;

            configUtils.set({
                privacy: {
                    useUpdateCheck: false
                }
            });

            updateCheck.__set__('request', function (_hostname, _reqObj) {
                hostname = _hostname;
                reqObj = _reqObj;

                return Promise.resolve({
                    statusCode: 200,
                    body: {version: 'something'}
                });
            });

            updateCheck.__set__('updateCheckData', updateCheckDataSpy);

            updateCheckDataSpy.returns(Promise.resolve({
                ghost_version: '0.11.11',
                blog_id: 'something',
                npm_version: 'something'
            }));

            return updateCheckRequest()
                .then(function () {
                    hostname.should.eql('https://updates.ghost.org');
                    reqObj.query.should.eql({
                        ghost_version: '0.11.11'
                    });

                    should.not.exist(reqObj.body);
                    reqObj.json.should.eql(true);
                    should.not.exist(reqObj.headers['Content-Length']);
                });
        });

        it('received 500 from the service', function () {
            const updateCheckRequest = updateCheck.__get__('updateCheckRequest');
            const updateCheckDataSpy = sinon.stub();
            let reqObj;
            let hostname;

            updateCheck.__set__('request', function (_hostname, _reqObj) {
                hostname = _hostname;
                reqObj = _reqObj;

                return Promise.reject({
                    statusCode: 500,
                    message: 'something went wrong'
                });
            });

            updateCheck.__set__('updateCheckData', updateCheckDataSpy);

            updateCheckDataSpy.returns(Promise.resolve({
                ghost_version: '0.11.11',
                blog_id: 'something',
                npm_version: 'something'
            }));

            return updateCheckRequest()
                .then(function () {
                    throw new Error('Should fail.');
                })
                .catch(function (err) {
                    err.message.should.eql('something went wrong');
                });
        });

        it('received 404 from the service', function () {
            const updateCheckRequest = updateCheck.__get__('updateCheckRequest');
            const updateCheckDataSpy = sinon.stub();
            let reqObj;
            let hostname;

            updateCheck.__set__('request', function (_hostname, _reqObj) {
                hostname = _hostname;
                reqObj = _reqObj;

                return Promise.reject({
                    statusCode: 404,
                    response: {
                        body: {
                            errors: [{detail: 'No Notifications available.'}]
                        }
                    }
                });
            });

            updateCheck.__set__('updateCheckData', updateCheckDataSpy);

            updateCheckDataSpy.returns(Promise.resolve({
                ghost_version: '0.11.11',
                blog_id: 'something',
                npm_version: 'something'
            }));

            return updateCheckRequest()
                .then(function () {
                    hostname.should.eql('https://updates.ghost.org');
                });
        });

        it('custom url', function () {
            const updateCheckRequest = updateCheck.__get__('updateCheckRequest');
            const updateCheckDataSpy = sinon.stub();
            let reqObj;
            let hostname;

            configUtils.set({
                updateCheck: {
                    url: 'http://localhost:3000'
                }
            });

            updateCheck.__set__('request', function (_hostname, _reqObj) {
                hostname = _hostname;
                reqObj = _reqObj;

                return Promise.resolve({
                    statusCode: 200,
                    body: {
                        version: 'something'
                    }
                });
            });

            updateCheck.__set__('updateCheckData', updateCheckDataSpy);

            updateCheckDataSpy.returns(Promise.resolve({
                ghost_version: '0.11.11',
                blog_id: 'something',
                npm_version: 'something'
            }));

            return updateCheckRequest()
                .then(function () {
                    hostname.should.eql('http://localhost:3000');
                });
        });
    });
});
