const debug = require('ghost-ignition').debug('web:oauth:app');
const {URL} = require('url');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const express = require('../../../shared/express');
const urlUtils = require('../../../shared/url-utils');
const shared = require('../shared');
const config = require('../../../shared/config');
const settingsCache = require('../../services/settings/cache');
const models = require('../../models');
const auth = require('../../services/auth');

function randomPassword() {
    return require('crypto').randomBytes(128).toString('hex');
}

module.exports = function setupOAuthApp() {
    debug('OAuth App setup start');
    const oauthApp = express('oauth');
    if (!config.get('enableDeveloperExperiments')) {
        debug('OAuth App setup skipped');
        return oauthApp;
    }

    // send 503 json response in case of maintenance
    oauthApp.use(shared.middlewares.maintenance);

    /**
     * Configure the passport.authenticate middleware
     * We need to configure it on each request because clientId and secret
     * will change (when the Owner is changing these settings)
     */
    function googleOAuthMiddleware(clientId, secret) {
        return (req, res, next) => {
            // TODO: use url config instead of the string /ghost

            //Create the callback url to be sent to Google
            const callbackUrl = new URL(urlUtils.getSiteUrl());
            callbackUrl.pathname = '/ghost/oauth/google/callback';

            passport.authenticate(new GoogleStrategy({
                clientID: clientId,
                clientSecret: secret,
                callbackURL: callbackUrl.href
            }, async function (accessToken, refreshToken, profile) {
                // This is the verify function that checks that a Google-authenticated user
                // is matching one of our users (or invite).

                if (req.user) {
                    // CASE: the user already has an active Ghost session
                    const emails = profile.emails.filter(email => email.verified === true).map(email => email.value);

                    if (!emails.includes(req.user.get('email'))) {
                        return res.redirect('/ghost/#/staff/?message=oauth-linking-failed');
                    }

                    // TODO: configure the oauth data for this user (row in the oauth table)

                    //Associate logged-in user with oauth account
                    req.user.set('password', randomPassword());
                    await req.user.save();
                } else {
                    // CASE: the user is logging-in or accepting an invite

                    //Find user in DB and log-in
                    //TODO: instead find the oauth row with the email use the provider id
                    const emails = profile.emails.filter(email => email.verified === true);
                    if (emails.length < 1) {
                        return res.redirect('/ghost/#/signin?message=login-failed');
                    }
                    const email = emails[0].value;

                    let user = await models.User.findOne({
                        email: email
                    });

                    if (!user) {
                        // CASE: the user is accepting an invite
                        // TODO: move this code in the invitations service
                        const options = {context: {internal: true}};
                        let invite = await models.Invite.findOne({email, status: 'sent'}, options);

                        if (!invite || invite.get('expires') < Date.now()) {
                            return res.redirect('/ghost/#/signin?message=login-failed');
                        }

                        //Accept invite
                        user = await models.User.add({
                            email: email,
                            name: profile.displayName,
                            password: randomPassword(),
                            roles: [invite.toJSON().role_id]
                        }, options);

                        await invite.destroy(options);

                        // TODO: create an oauth model link to user
                    }

                    req.user = user;
                }

                await auth.session.sessionService.createSessionForUser(req, res, req.user);

                return res.redirect('/ghost/');
            }), {
                scope: ['profile', 'email'],
                session: false,
                prompt: 'consent',
                accessType: 'offline'
            })(req, res, next);
        };
    }

    oauthApp.get('/:provider', auth.authenticate.authenticateAdminApi, (req, res, next) => {
        if (req.params.provider !== 'google') {
            return res.sendStatus(404);
        }

        const clientId = settingsCache.get('oauth_client_id');
        const secret = settingsCache.get('oauth_client_secret');

        if (clientId && secret) {
            return googleOAuthMiddleware(clientId, secret)(req, res, next);
        }

        res.sendStatus(404);
    });

    oauthApp.get('/:provider/callback', (req, res, next) => {
        // Set the referrer as the ghost instance domain so that the session is linked to the ghost instance domain
        req.headers.referrer = urlUtils.getSiteUrl();
        next();
    }, auth.authenticate.authenticateAdminApi, (req, res, next) => {
        if (req.params.provider !== 'google') {
            return res.sendStatus(404);
        }

        const clientId = settingsCache.get('oauth_client_id');
        const secret = settingsCache.get('oauth_client_secret');

        if (clientId && secret) {
            return googleOAuthMiddleware(clientId, secret)(req, res, next);
        }

        res.sendStatus(404);
    });

    debug('OAuth App setup end');

    return oauthApp;
};
