/**
 * server.js
 * Copyright (c) 2000 - 2015 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * DongHee Yang <donghee.yang@samsung.com>
 * Sungmin Kim <sm.art.kim@samsung.com>
 * Jiil Hyoun <jiil.hyoun@samsung.com>
 * Jonghwan Park <iwin100.park@samsung.com>
 * Kitae Kim <kt920.kim@samsung.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 * - S-Core Co., Ltd
**/

var dibs = require('../../core/dibs');
var DError = require('../../core/exception');
var _ = require('underscore');
var async = require('async');


/**
 * @module servers/approval-manager/server
 */


/**
 * @function createServer
 * @param {string} serverId - serverId
 * @returns {string}
 * @memberOf module:servers/approval-manager/server
 */

module.exports.createServer = function (serverId) {
    ApprovalManagerServer.prototype = dibs.BaseServer.createServer(serverId, 'approvalmgr');

    return new ApprovalManagerServer();
};


function ApprovalManagerServer() {
    this.name = 'approval manager';

    var approvalEventListeners = {}; // approvalId  -> listenerList
    var self = this;

    // CALLBACK: for creating server's default configuration
    this.getDefaultConfiguration = function (baseConfig) {
        return baseConfig;
    };

    this.OnServerStarting = function (callback) {
        callback(null);
    };

    this.OnServerStarted = function (callback) {
        callback(null);
    };

    // callback(err, approval)
    this.addApprovalInternal = function (email, subject, contents, summary, routes, type, callback) {
        // create new approval from prjName
        var approval = null;
        self.log.info('adding new approval \'' + subject + '\' from ' + email);
        async.waterfall([
            function (cb) {
                dibs.rpc.datamgr.addApproval(email, subject, contents, summary, routes, type, cb);
            },
            function (newApproval, cb) {
                // handle exception case.
                if (newApproval.submissionStatus === 'SUBMITTED') {
                    cb(null, newApproval);
                } else {
                    cb(new DError('APPROVALMGR002', {
                        id: newApproval.id,
                        status: newApproval.submissionStatus
                    }), null);
                }
            },
            function (newApproval, cb) {
                approval = newApproval;
                notify(approval, email, cb);
            },
            function (approval, cb) {
                modifyApprovalInternal(approval, 'APPROVAL_ADDED', cb);
            }
        ],
            function (err) {
                if (err) {
                    self.log.info(err);
                }
                self.log.info('Added a new approval #' + approval.id + (err ? ' failed' : ' succeeded'));
                callback(err, approval);
            });
    };



    // callback(err, approvals)
    this.searchApprovalsInternal = function (queryObject, callback) {
        dibs.rpc.datamgr.searchApprovals(queryObject, function (err, approvals) {
            if (err) {
                callback(err, []);
            } else {
                callback(err, approvals);
            }
        });
    };


    // callback(err, approval)
    function getApprovalInternal(approvalId, callback) {
        dibs.thisServer.searchApprovalsInternal({
            id: approvalId
        }, function (err, approvals) {
            if (err) {
                callback(err, null);
            } else {
                callback(err, approvals[0]);
            }
        });
    }
    this.getApprovalInternal = getApprovalInternal;


    // callback(err, approval)
    function modifyApprovalInternal(approval, statusType, callback) {
        dibs.rpc.datamgr.updateApproval(approval, function (err) {
            if (err) {
                self.log.info('Modify approval failed #' + approval.id + ' : ' + err.message);
                callback(err, null);
            } else {
                self.log.info('Modified approval #' + approval.id);
                self.emitEvent({
                    event: 'APPROVAL_STATUS_CHANGED',
                    status: statusType,
                    approvalId: approval.id,
                    approval: approval
                });
                self.log.info('Modify done');
                callback(err, approval);
            }
        });
    }
    this.modifyApprovalInternal = modifyApprovalInternal;

    // callback(err)
    this.approveInternal = function (approvalId, email, message, callback) {
        approvalProcess(approvalId, email, 'APPROVE', message, callback);
    };

    // callback(err)
    this.rejectInternal = function (approvalId, email, message, callback) {
        approvalProcess(approvalId, email, 'REJECT', message, callback);
    };

    // callback(err)
    this.cancelInternal = function (approvalId, email, message, callback) {
        approvalProcess(approvalId, email, 'CANCEL', message, callback);
    };

    function approvalProcess(approvalId, email, approvalType, message, callback) {
        var statusType = null;
        var masterServer = dibs.getServersByType('master')[0];

        if (masterServer.status !== 'RUNNING') {
            callback(new DError('APPROVALMGR005', {
                status: masterServer.status
            }));
            return;
        }

        switch (approvalType) {
        case 'APPROVE':
            statusType = 'APPROVED';
            break;
        case 'REJECT':
            statusType = 'REJECTED';
            break;
        case 'CANCEL':
            statusType = 'CANCELED';
            break;
        default:
            break;
        }

        if (statusType) {
            self.log.info('Starting ' + approvalType + ' process from ' + email);
            async.waterfall([
                function (cb) {
                    getApprovalInternal(approvalId, cb);
                },
                function (approval, cb) {
                    // handle exception case.
                    if (statusType === 'CANCELED') {
                        if (approval.submissionStatus !== 'SUBMITTED') {
                            cb(new DError('APPROVALMGR003', {
                                id: approval.id,
                                status: approval.submissionStatus
                            }), null);
                        } else {
                            cb(null, approval);
                        }
                    } else {
                        cb(null, approval);
                    }
                },
                function (approval, cb) {
                    makeRouteDecision(approval, email, message, statusType, cb);
                },
                function (approval, cb) {
                    notify(approval, email, cb);
                },
                function (approval, cb) {
                    if (approval.submissionStatus !== 'APPROVED' && statusType === 'APPROVED') {
                        modifyApprovalInternal(approval, 'ROUTE_APPROVED', cb);
                    } else {
                        modifyApprovalInternal(approval, statusType, cb);
                    }
                }
            ],
                function (err) {
                    callback(err);
                });
        } else {
            callback(new DError('APPROVALMGR001', {
                type: approvalType
            }));
        }
    }

    // callback(err, approval)
    function makeRouteDecision(approval, email, message, statusType, callback) {
        var sortedRoutes = _.sortBy(approval.approvalRoutes, function (route) {
            return route.order;
        });

        if (statusType === 'CANCELED') {
            var draftRoute = _.find(sortedRoutes, function (route) {
                return (route.status == 'SUBMITTED');
            });
            var approverRoute;

            async.waterfall([
                function (cb) {
                    if (!draftRoute) {
                        self.log.error('failed to find a drafter for approval #' + approval.id);
                        cb(new Error('cannot find a drafter'));
                    } else {
                        cb(null);
                    }
                },
                function (cb) {
                    dibs.rpc.datamgr.searchUsers({
                        email: email
                    }, cb);
                },
                function (users, cb) {
                    if (draftRoute.email !== email) {
                        if (users[0].isAdmin) {
                            self.log.info(email + '(admin) cancel approval #' + approval.id);
                            cb(null);
                        } else {
                            self.log.error(email + ' is not the drafter of #' + approval.id);
                            cb(new Error(email + ' is not the drafter of #' + approval.id));
                        }
                    } else {
                        cb(null);
                    }
                },
                function (cb) {
                    draftRoute.status = statusType;
                    draftRoute.message = message;
                    draftRoute.date = new Date();

                    approverRoute = _.find(sortedRoutes, function (route) {
                        return (route.status == 'WAIT');
                    });

                    if (!approverRoute) {
                        self.log.error('failed to find an approver for approval #' + approval.id);
                        cb(new Error('cannot find an approver'));
                    } else {
                        cb(null);
                    }
                }],
                function (err) {
                    if (err) {
                        callback(err, approval);
                    } else {
                        var decidedRoutes = [];
                        decidedRoutes.push(draftRoute);
                        decidedRoutes.push(approverRoute);

                        approval.approvalRoutes = decidedRoutes;
                        callback(null, approval);
                    }
                }
            );
        } else {
            var currentRoute = _.find(sortedRoutes, function (route) {
                return (route.status == 'WAIT');
            });

            async.waterfall([
                function (cb) {
                    if (!currentRoute) {
                        self.log.error('All approvalRoute of approval #' + approval.id + ' finished already');
                        cb(new Error('All approvalRoute of approval #' + approval.id + ' finished already'));
                    } else {
                        cb(null);
                    }
                },
                function (cb) {
                    dibs.rpc.datamgr.searchUsers({
                        email: email
                    }, cb);
                },
                function (users, cb) {
                    if (currentRoute.email !== email) {
                        if (users[0].isAdmin) {
                            self.log.info(email + '(admin) approved the approval #' + approval.id);
                            cb(null);
                        } else {
                            self.log.error(email + ' is not decision maker of approval #' + approval.id);
                            cb(new Error(email + ' is not decision maker of approval #' + approval.id));
                        }
                    } else {
                        cb(null);
                    }
                }],
                function (err) {
                    if (err) {
                        callback(err, approval);
                    } else {
                        var decidedRoutes = _.map(sortedRoutes, function (route) {
                            if (route.order == currentRoute.order) {
                                route.status = statusType;
                                route.message = message;
                                route.date = new Date();
                            }
                            return route;
                        });
                        approval.approvalRoutes = decidedRoutes;
                        callback(null, approval);
                    }
                }
            );
        }
    }

    // callback(err, approval)
    // approval status : SUBMITTED / APPROVED / REJECTED / CANCELED
    // approvalRoutes status :
    // type DRAFT : SUBMITTED / CANCELED
    // type APPROVE : WAIT / APPROVED / REJECTED
    // type CONSENT : WAIT / APPROVED / REJECTED
    // type NOTIFICATION : WAIT / NOTIFIED
    function notify(approval, email, callback) {
        var sortedRoutes = _.sortBy(approval.approvalRoutes,
            function (route) {
                return route.order;
            });
        var approvedRoutes = [];
        var exit = false;
        _.each(sortedRoutes, function (route) {
            if (!exit) {
                switch (route.status) {
                case 'SUBMITTED':
                    if (!route.date) {
                        route.date = new Date();
                    }
                    approvedRoutes.push(route);
                    break;
                case 'WAIT':
                    var message = generateEmailBody(approval);
                    self.log.info('notification approval #' + approval.id + ' to ' + route.email + ' as ' + route.type);

                    if (route.type == 'NOTIFICATION') {
                        route.status = 'NOTIFIED';
                        route.date = new Date();
                        self.log.info('route status is changed to \'NOTIFIED\'');
                    } else {
                        exit = true;
                    }

                    if (dibs.getServersByType('messenger')[0]) {
                        dibs.rpc.messenger.notify('email', '[' + route.type + ']' + approval.subject, route.email, message, function (err) {
                            if (err) {
                                self.log.info(err);
                            }
                            self.log.info('sending notification email for approval #' + approval.id +
                                ' is' + (err ? ' failed' : ' succeeded'));
                        });
                    } else {
                        self.log.info('sending notification email for approval #' + approval.id + ' is failed(messenger server is not exists)');
                    }
                    break;
                case 'REJECTED':
                    exit = true;
                    approval.submissionStatus = 'REJECTED';
                    approval.submissionDate = new Date();

                    var rejectSubject = '[' + route.type + '][REJECTED]' + approval.subject;
                    var rejectMessage = 'reason : \n\n' + route.message +
                        '\n\n by ' + email +
                        '\n\n' + generateEmailBody(approval);

                    async.eachLimit(approvedRoutes, 10,
                        function (approvedRoute, cb) {
                            self.log.info('notification approval #' + approval.id + ' to ' + approvedRoute.email + ' as ' + route.type);

                            if (dibs.getServersByType('messenger')[0]) {
                                dibs.rpc.messenger.notify('email', rejectSubject, approvedRoute.email, rejectMessage, cb);
                            } else {
                                cb(new Error('messenger server is not exists'));
                            }
                        },
                        function (err) {
                            if (err) {
                                self.log.info(err);
                            }
                            self.log.info('sending notification email for reject approval #' + approval.id +
                                ' is' + (err ? ' failed' : ' succeeded'));
                        }
                    );
                    break;
                case 'CANCELED':
                    exit = true;
                    approval.submissionStatus = 'CANCELED';

                    var cancelSubject = '[' + route.type + '][CANCELED]' + approval.subject;
                    var cancelMessage = 'reason : \n\n' + route.message +
                        '\n\n by ' + email +
                        '\n\n' + generateEmailBody(approval);

                    async.eachLimit(approval.approvalRoutes, 10,
                        function (canceledRoute, cb) {
                            self.log.info('notification approval #' + approval.id + ' to ' + canceledRoute.email + ' as ' + route.type);

                            if (dibs.getServersByType('messenger')[0]) {
                                dibs.rpc.messenger.notify('email', cancelSubject, canceledRoute.email, cancelMessage, cb);
                            } else {
                                cb(new Error('messenger server is not exists'));
                            }
                        },
                        function (err) {
                            if (err) {
                                self.log.info(err);
                            }
                            self.log.info('sending notification email for cancel approval #' + approval.id +
                                ' is' + (err ? ' failed' : ' succeeded'));
                        }
                    );
                    break;
                case 'APPROVED':
                default : // NOTIFIED
                    approvedRoutes.push(route);
                    break;
                }
            }
        }
        );

        approval.approvalRoutes = sortedRoutes;
        var nextWait = _.find(sortedRoutes, function (route) {
            return (route.status == 'WAIT');
        });

        if (approval.submissionStatus == 'SUBMITTED' && nextWait === undefined) {
            approval.submissionStatus = 'APPROVED';
            approval.submissionDate = new Date();
        }
        callback(null, approval);
    }

    function generateEmailBody(approval) {
        var message = '(' + approval.type + ') ' + approval.subject + '\n\n' + approval.contents + '\n\n';

        _.each(approval.approvalRoutes, function (route) {
            message += '\t' + '(' + route.type + ') : [' + route.status + '] ' + route.email + '\n';
            if (route.message) {
                message += '\t\t' + route.message + '\n\n';
            }
        });

        return message + '\n\n\nBuild summary : \n' + approval.summary;
    }
}
