/**
 * Copyright 2015 IBM Corp.
 *
 * 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.
 **/

var BearerStrategy = require('passport-http-bearer').Strategy;
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;

var passport = require("passport");
var crypto = require("crypto");
var util = require("util");

var Tokens = require("./tokens");
var Users = require("./users");
var Clients = require("./clients");
var permissions = require("./permissions");

var log;

var bearerStrategy = function (accessToken, done) {
    // is this a valid token?
    Tokens.get(accessToken).then(function(token) {
        if (token) {
            Users.get(token.user).then(function(user) {
                if (user) {
                    done(null,user,{scope:token.scope});
                } else {
                    log.audit({event: "auth.invalid-token"});
                    done(null,false);
                }
            });
        } else {
            log.audit({event: "auth.invalid-token"});
            done(null,false);
        }
    });
}
bearerStrategy.BearerStrategy = new BearerStrategy(bearerStrategy);

var clientPasswordStrategy = function(clientId, clientSecret, done) {
    Clients.get(clientId).then(function(client) {
        if (client && client.secret == clientSecret) {
            done(null,client);
        } else {
            log.audit({event: "auth.invalid-client",client:clientId});
            done(null,false);
        }
    });
}
clientPasswordStrategy.ClientPasswordStrategy = new ClientPasswordStrategy(clientPasswordStrategy);

var loginAttempts = [];
var loginSignInWindow = 600000; // 10 minutes


var passwordTokenExchange = function(client, username, password, scope, done) {
    var now = Date.now();
    loginAttempts = loginAttempts.filter(function(logEntry) {
        return logEntry.time + loginSignInWindow > now;
    });
    loginAttempts.push({time:now, user:username});
    var attemptCount = 0;
    loginAttempts.forEach(function(logEntry) {
        /* istanbul ignore else */
        if (logEntry.user == username) {
            attemptCount++;
        }
    });
    if (attemptCount > 5) {
        log.audit({event: "auth.login.fail.too-many-attempts",username:username,client:client.id});
        done(new Error("Too many login attempts. Wait 10 minutes and try again"),false);
        return;
    }

    Users.authenticate(username,password).then(function(user) {
        if (user) {
            if (scope === "") {
                scope = user.permissions;
            }
            if (permissions.hasPermission(user.permissions,scope)) {
                loginAttempts = loginAttempts.filter(function(logEntry) {
                    return logEntry.user !== username;
                });
                Tokens.create(username,client.id,scope).then(function(tokens) {
                    log.audit({event: "auth.login",username:username,client:client.id,scope:scope});
                    done(null,tokens.accessToken,null,{expires_in:tokens.expires_in});
                });
            } else {
                log.audit({event: "auth.login.fail.permissions",username:username,client:client.id,scope:scope});
                done(null,false);
            }
        } else {
            log.audit({event: "auth.login.fail.credentials",username:username,client:client.id,scope:scope});
            done(null,false);
        }
    });
}

function AnonymousStrategy() {
  passport.Strategy.call(this);
  this.name = 'anon';
}
util.inherits(AnonymousStrategy, passport.Strategy);
AnonymousStrategy.prototype.authenticate = function(req) {
    var self = this;
    Users.default().then(function(anon) {
        if (anon) {
            self.success(anon,{scope:anon.permissions});
        } else {
            self.fail(401);
        }
    });
}

module.exports = {
    init: function(runtime) {
        log = runtime.log;
    },
    bearerStrategy: bearerStrategy,
    clientPasswordStrategy: clientPasswordStrategy,
    passwordTokenExchange: passwordTokenExchange,
    anonymousStrategy: new AnonymousStrategy()
}