const passport = require('passport');
const expressSession = require('express-session');
const config = require('./config');

// set up database for express session
const MongoStore = require('connect-mongo')(expressSession);
const mongoose = require('mongoose');
const db = require("qmi-cloud-common/mongo");
const axios = require('axios');
const path = require('path');
const fs = require('fs');

// Start QuickStart here

var OIDCStrategy = require('passport-azure-ad').OIDCStrategy;

/******************************************************************************
 * Set up passport in the app 
 ******************************************************************************/

//-----------------------------------------------------------------------------
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session.  Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing.
//-----------------------------------------------------------------------------
passport.serializeUser(function(user, done) {
  done(null, user.oid);
});

passport.deserializeUser(function(oid, done) {
    _findByOid(oid, function (err, user) {
        done(err, user);
    });
});

var _findByOid = async function(oid, fn) {
    var mongouser = await db.user.getOne({"oid": oid});
    if (mongouser && mongouser.oid === oid){
        return fn(null, mongouser);
    } else {
        return fn(null, null);
    }
};

//-----------------------------------------------------------------------------
// Use the OIDCStrategy within Passport.
// 
// Strategies in passport require a `verify` function, which accepts credentials
// (in this case, the `oid` claim in id_token), and invoke a callback to find
// the corresponding user object.
// 
// The following are the accepted prototypes for the `verify` function
// (1) function(iss, sub, done)
// (2) function(iss, sub, profile, done)
// (3) function(iss, sub, profile, access_token, refresh_token, done)
// (4) function(iss, sub, profile, access_token, refresh_token, params, done)
// (5) function(iss, sub, profile, jwtClaims, access_token, refresh_token, params, done)
// (6) prototype (1)-(5) with an additional `req` parameter as the first parameter
//
// To do prototype (6), passReqToCallback must be set to true in the config.
//-----------------------------------------------------------------------------
passport.use(new OIDCStrategy({
    identityMetadata: config.creds.identityMetadata,
    clientID: config.creds.clientID,
    responseType: config.creds.responseType,
    responseMode: config.creds.responseMode,
    redirectUrl: config.creds.redirectUrl,
    allowHttpForRedirectUrl: config.creds.allowHttpForRedirectUrl,
    clientSecret: config.creds.clientSecret,
    validateIssuer: config.creds.validateIssuer,
    isB2C: config.creds.isB2C,
    issuer: config.creds.issuer,
    passReqToCallback: config.creds.passReqToCallback,
    scope: config.creds.scope,
    loggingLevel: config.creds.loggingLevel,
    nonceLifetime: config.creds.nonceLifetime,
    nonceMaxAmount: config.creds.nonceMaxAmount,
    useCookieInsteadOfSession: config.creds.useCookieInsteadOfSession,
    cookieEncryptionKeys: config.creds.cookieEncryptionKeys,
    clockSkew: config.creds.clockSkew,
  },
  function(iss, sub, profile, jwtClaims, accessToken, refreshToken, params, done) {
    
    if ( !profile.oid ) {
        return done(new Error("No oid found"), null);
    }
    //console.log("accessToken", accessToken);
    //console.log("iss", iss);
    //console.log("sub", sub);
    //console.log("refreshToken", refreshToken);
    //console.log("jwtClaims", jwtClaims);
    //console.log("params", params);
    console.log(`Passport# new login from: ${profile.upn} (${profile.displayName})` );

    //Save user photo
    axios({
        method: 'get',
        url: 'https://graph.microsoft.com/v1.0/me/photo/$value',
        responseType: 'stream',
        headers: { 'Authorization' : 'Bearer '+accessToken }
    }).then(function (response) {
        response.data.pipe(fs.createWriteStream(path.resolve(__dirname, '..', 'photos', `${profile.oid}.jpg`)));
    }).catch(function(err){
        console.log('Passport# Warning: No picture found');
    });
    
    // asynchronous verification, for effect...
    process.nextTick(function () {
      _findByOid(profile.oid, async function(err, user) {
            if (err) {
                return done(err);
            }
            if (!user) {   
                // "Auto-registration"
                user = await db.user.add({
                    "oid": profile.oid, 
                    "upn": profile.upn, 
                    "displayName": profile.displayName,
                    "lastLogin": new Date()
                });
                return done(null, user);
            }
            db.user.update(user._id, {"lastLogin": new Date()});
            return done(null, user);
      });
    });
  }
));

module.exports.init = function(app){
    
    // set up session middleware
    if (config.useMongoDBSessionStore) {
        //mongoose.connect(config.databaseUri);
        app.use(expressSession({
            secret: 'secret',
            cookie: {maxAge: config.mongoDBSessionMaxAge * 1000},
            store: new MongoStore({
                mongooseConnection: mongoose.connection,
                autoRemove: 'interval',
                autoRemoveInterval: 10
                //clear_interval: config.mongoDBSessionMaxAge
            }),
            resave: true,
            saveUninitialized: false
        }));
    } else {
        app.use(expressSession({ secret: 'keyboard cat', resave: true, saveUninitialized: false }));
    }


    // Initialize Passport!  Also use passport.session() middleware, to support
    // persistent login sessions (recommended).
    app.use(passport.initialize());
    app.use(passport.session());

    app.get('/login',
        function(req, res, next) {
            passport.authenticate('azuread-openidconnect', 
                { 
                response: res,                      // required
                resourceURL: config.resourceURL,    // optional. Provide a value if you want to specify the resource.
                //customState: 'my_state',            // optional. Provide a value if you want to provide custom state value.
                failureRedirect: '/', 
                session: false
                }
            )(req, res, next);
        },
        function(req, res) {
            res.redirect('/');
        }
    );

    // 'GET returnURL'
    // `passport.authenticate` will try to authenticate the content returned in
    // query (such as authorization code). If authentication fails, user will be
    // redirected to '/' (home page); otherwise, it passes to the next middleware.
    app.get('/auth/openid/return',
        function(req, res, next) {
            passport.authenticate('azuread-openidconnect', 
                { 
                response: res,                      // required
                failureRedirect: '/'  
                }
            )(req, res, next);
        },
        function(req, res) {
            console.log('Passport# We received a return from AzureAD.');
            res.redirect('/provisions');
        }
    );

    // 'POST returnURL'
    // `passport.authenticate` will try to authenticate the content returned in
    // body (such as authorization code). If authentication fails, user will be
    // redirected to '/' (home page); otherwise, it passes to the next middleware.
    app.post('/auth/openid/return',
        function(req, res, next) {
            passport.authenticate('azuread-openidconnect', 
                { 
                response: res,                      // required
                failureRedirect: '/'  
                }
            )(req, res, next);
        },
        function(req, res) {
            console.log('Passport# We received a return from AzureAD.');
            res.redirect('/provisions');
        }
    );

    // 'logout' route, logout from passport, and destroy the session with AAD.
    app.get('/logout', function(req, res){
        req.session.destroy(function(err) {
            req.logOut();
            res.redirect(config.destroySessionUrl);
        });
    });
};

async function isApiKeyAuthenticated(req) {
    let key = req.query.apiKey || req.get('QMI-ApiKey');
    if ( key ) {  
        var result = await db.apiKey.getOne({"apiKey": key}); 
        if ( result ) {
            req.user = result.user;
            return true;
        } else {
            return false;
        }
        
     } else {
        return false;
    }
}

module.exports.ensureAuthenticatedDoLogin = async function(req, res, next) {
    if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) { 
        return next(); 
    }
    res.redirect('/login');
};

module.exports.ensureAuthenticated = async function(req, res, next) {
    if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) { 
        return next(); 
    }
    res.status(401).send({"error": "Unauthorized"});
};

module.exports.ensureAuthenticatedAndAdmin = async function(req, res, next) {
    if ( ( await isApiKeyAuthenticated(req) || req.isAuthenticated()) && (req.user.role === 'admin' || req.user.role === 'superadmin') ) { 
        return next(); 
    }
    res.status(401).send({"error": "Unauthorized"});
};

module.exports.ensureAuthenticatedAndIsMe = async function (req, res, next) {
    if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) { 
        var userId = (req.params.userId === 'me')? req.user._id : req.params.userId;
        if ( req.user._id == userId || req.user.role === 'admin' || req.user.role === 'superadmin' ) {
            return next(); 
        } else {
            return res.status(401).send("Error: Unauthorized");
        }
    }
    return res.status(401).send("Error: Unauthorized");
};