/** 
 * @file Initialise et configure le serveur Express sur lequel tourne le back.
 * 
 * La configuration inclut tout le _middleware_ définissant les API et les services
 * nécessaire utilisés, comme `express-session`, GraphiQL, GraphQL Voyager.
 * 
 * TODO: changer cette description... ^
 * TODD: qu'arrive-t-il aux requetes avec un cookie expire? elles ne sont traitees ni par passport.session() ni par passport.authenticate('ldapauth')...
 * 
 * @author manifold, kadabra
*/

import express from 'express';
import bodyParser from 'body-parser';
// packages pour graphql
import { express as graphqlVoyager } from 'graphql-voyager/middleware';
import graphqlHTTP from 'express-graphql'; // new name of 'graphql-server-express' (mai 2018). cf npmjs.com. 
import schema from './graphql/schema';
// packages pour adminview
import { ensureLoggedIn } from 'connect-ensure-login';
import flash from 'connect-flash';
import router from './admin_view/admin_router';
// packages pour l'authentification
import passport from 'passport';
import session from 'express-session';
import cookieParser from 'cookie-parser';
import cors from 'cors';
// packages divers
import favicon from 'serve-favicon';
import morgan from 'morgan';
// packages pour pouvoir importer depuis des fichiers de config
import path from 'path';
import fs from 'fs';



const app = express(); // "The app object conventionally denotes the Express application" (https://expressjs.com/en/4x/api.html#app)

// Parse incoming HTTP request bodies, available under the req.body property. cf www.npmjs.com/package/body-parser
app.use(bodyParser.json()); //parses bodies of media type "application/json"
app.use(bodyParser.urlencoded({ //parses bodies of media type "application/x-www-form-urlencoded"
    extended: true //use qs library (quoi que ca veuille dire o.O)
}));
app.use(cookieParser()); //parses Cookie header and populate req.cookies with an object keyed by the cookie names. was necessary for express-session before its v1.5.0. on peut probablement l'enlever desormais.

//GHETTO
// Config de passport pour l'authentification ldap. 
// Ne fait que *configurer* passport pour la strategie 'ldap' (pas d'incidence sur la strategie 'session' normalement)
import './config_passport.js';



/**
 * @desc TRUCS DIVERS
 */

// cache le fait que l'application tourne sous Express dans le header HTTP.
app.disable('x-powered-by');
// Morgan is middleware for logging requests
app.use(morgan('dev'));
// favicon: capital sigma symbol
app.use(favicon(path.resolve('./', 'assets', 'favicon.ico')));
// specifies path to static assets. ......je comprends pas ce que c'est. TODO
app.use('/assets', express.static(path.resolve('./', 'assets')));

/**
 * FIN TRUCS DIVERS
 */





/**
 * @desc AUTHENTIFICATION POUR LES REQUETES POSSEDANT UN COOKIE ET PROVENANT D'UN UTILISATEUR DEJA AUTHENTIFIE
 * Remarque: introduit aussi les middlewares session et passport, qui sont aussi utiles pour l'authentification dans les autres cas.
 */

const configPath = path.resolve('./', 'ldap_config.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
// WTF??? why is sessionSecret in ldap_config.json? it has nothing to do with ldap. TODO

// defines parameters for *session store*. (adds field req.session and do some magic stuff)
// basically, searches for a session matching the received cookie and, if found, adds field req.blasomethingbla containing serialized object representing user (i.e. similar to what passport.serializeUser() could produce)
// TODO: it is important to configure this right!!! please check out https://www.npmjs.com/package/express-session and make sure you understand the way session is stored. (en vrai c'est vraiment important...)
app.use(session({
    secret: config.sessionSecret,
    resave: true,
    saveUninitialized: false,
    //store: // TODO: change this. express-session doc warns that default value is ok to use for development only
}));
app.use(passport.initialize()); //initialize Passport. (adds hidden field req._passport and do some magic stuff)
//GHETTO
//app.use(passport.session()); //this is equivalent to app.use(passport.authenticate('session'))
app.use(passport.session(), (req, res, next)=>{
    console.log("Used passport.session()");
    console.log(`passport.session() found user: ${req.user ? req.user.uid : "none"}`);
    console.log("passport.session() user is authenticated:", req.isAuthenticated());
    next();
}); //this is equivalent to app.use(passport.authenticate('session'))
// *aucun* effet sur les requetes n'ayant pas ete reconnues par app.use(session(...)) (e.g. les requetes sans cookie ou les requetes avec cookie expired). source: lecture directe du code passport/lib/strategies/session.js sur github... :/

/*
app.use((req, res, next) => {
    console.log("Finished trying to authentify request as an existing session");
    console.log("req.user: "+req.user);
});
*/

/**
 * FIN AUTHENTIFICATION POUR LES REQUETES POSSEDANT UN COOKIE ET PROVENANT D'UN UTILISATEUR DEJA AUTHENTIFIE
 */





/**
 * @desc AUTHENTIFICATION POUR LES REQUETES DE CONNEXION VIA LDAP VENANT DU FRONT
 * i.e. endpoint for *frontend's* authentication requests (login through adminview/avlogin are caught by the router in admin_router.js)
 * i.e. quand l'utilisateur submit le formulaire de login avec ses identifiants/mdp dans le front
 * Remarque: configure aussi passport pour l'authentification ldap, ce qui est aussi utile pour les requetes de connexion via ldap venant de adminview
 */

const FRONTEND_SERVER_URL = 'change this to frontend server IP address';
const FRONTEND_SERVER_URL_LOCAL = 'http://localhost:8888';

// Options de configuration pour le _middleware_ `cors`.
// CORS = Cross Origin Resource Sharing
const corsOptions = {
    origin: FRONTEND_SERVER_URL_LOCAL, // Configures the Access-Control-Allow-Origin CORS header. i.e. specifies that sigma-back wants to make resources accessible to this site (and this site only)
    credentials: true // Configures the Access-Control-Allow-Credentials CORS header. i.e. allows cookies to be included on cross-origin requests
};
app.use(cors(corsOptions));
 
//GHETTO
// Config de passport pour l'authentification ldap. Ne fait que *configurer* passport (aucun passport.authenticate() n'est appele, par exemple)
import './config_passport.js';


//with custom callback:
//http://www.passportjs.org/docs/authenticate/#custom-callback
// http://toon.io/understanding-passportjs-authentication-flow/
app.post('/login', (req, res, next) => {
    console.log("Received an authentication request to /login");
    passport.authenticate('ldapauth', (err, user, info) => {
        console.log("| Entering passport.authenticate('ldapauth', - ) callback");
        // If an exception occurred
        if (err) {
            console.log("| Error when trying to passport.authenticate with ldapauth");
            console.log(err);
            return res.status(err.status).json({
                message: "Exception raised in backend process during authentication: " + err,
                authSucceeded: false
            });
            // return next(err); // handle error? or drop request and answer with res.json()?
        }
        // If authentication failed, user will be set to false
        if (!user) {
            console.log("| Authentication failed, passport.authenticate did not return a user. ");
            return res.status(401).json({
                message: "Authentication failed: " + info.message,
                authSucceeded: false
            });
        }

        req.login(user, (err) => {
            // If an exception occurred at login
            if (err) {
                console.log("| Error when trying to req.login in callback in passport.authenticate('ldapauth', - )");
                console.log(err);
                return res.status(err.status).json({
                    message: "Exception raised in backend process during login: " + err,
                    authSucceeded: false
                });
                // return next(err); // handle error? or drop request and answer with res.json()?
            }
            // If all went well
            console.log("| Authentication succeeded! :-)");
            // passport.authenticate automatically includes a Set-Cookie HTTP header in the response. The JSON body is just to signal the frontend that all went well
            return res.status(200).json({
                message: 'Authentication succeeded',
                authSucceeded: true
            });
        });
    })(req, res, next);
});

//without custom callback:
/*
// http://toon.io/understanding-passportjs-authentication-flow/
app.post('/login',
    passport.authenticate('ldapauth'),
    function (req, res) {
        // If this function gets called, authentication was successful.
        // `req.user` contains the authenticated user.
        console.log("Frontend authentication succeeded");
        res.json({
            message: 'Authentication succeeded',
            authSucceeded: true
        }); 
    }
);
*/

/**
 * FIN AUTHENTIFICATION POUR LES REQUETES DE CONNEXION VIA LDAP VENANT DU FRONT
 */





/**
 * @desc API GRAPHQL
 */

import { dn, passwd } from "../ldap_connexion_config.json"; // default user

app.use('/graphql', 
    graphqlHTTP(async (req, res, params) => {
        // vary the options *on a per-request basis*
        let uid;
        let password;

        console.log("Responding to graphql request...");
        console.log(`| User: ${req.user ? req.user.uid : "none"}`);
        console.log("| User is authenticated:",req.isAuthenticated());
        
        if(req.isAuthenticated()) {
            console.log("graphql API is receiving a request from an authenticated user! \\o/");
            try {
                uid = req.user.uid;
                password = "mythe";
            } catch (err) {
                console.log("Error: req is authenticated, but pb when trying to extract uid from req.user. Probably user was either not serialized or not deserialized properly");
                console.log(err);
            }
        } else {
            // FOR DEVELOPMENT ONLY. for production, replace with a "publicUser" or "notLoggedInUser" or something.
            uid = dn.split("=")[1].split(",")[0];
            password = passwd;
        }

        return {
            schema,
            graphiql: true, // gives access to graphiql if request is detected to be from browser (je crois)
            context: { user: { uid: uid, password: password } } // accessible in every single resolver as the third argument
        };
    })
);

/**
 * FIN API GRAPHQL
 */





/**
 * @desc SETUP DE ADMINVIEW, L'INTERFACE ADMIN DES BDD
 * Remarque: le graphiql est desormais integre a express-graphql (mai 2018), donc il n'est plus possible de le proteger par connect-ensure-login, ni donc de considerer qu'il fait partie de l'adminview.
 * C'est relativement grave, car n'importe qui se connectant directement a [adresse_IP_de_roued]/graphql peut faire des requetes a la base de donnees de sigma
 * Donc il faudra retirer cette fonctionnalite en production ; or elle est pratique, meme en production, pour des sanity checks.
 * ...bref integrer graphiql a express-graphql etait completement con.
 */

// setting up view engine for pug, for adminview
console.log("Running at", __dirname);
let viewpath = path.resolve(__dirname, 'views');
app.set('views', viewpath);
app.set('view engine', 'pug');

// connect-flash is middleware for flashing messages, used in adminview
app.use(flash());

// GraphQL voyager affiche une représentation sous forme de graphe du schema GraphQL
// accessible depuis adminview
app.use('/voyager',
    /*ensureLoggedIn('/login'),*/
    graphqlVoyager({ endpointUrl: '/graphql' })
);

// on utilise un express.Router qui sert a creer un "sous-middleware stack".
app.use('/adminview', router); // catches and resolves HTTP requests to paths '/adminview/*'

// catch all other GET requests.
// il est bien sur essentiel de mettre ceci a la toute fin du middleware stack !
app.get('/*',
    ((req, res, next) => res.redirect('/adminview'))
);

/**
 * FIN SETUP DE ADMINVIEW, L'INTERFACE ADMIN DES BDD
 */


export default app;