const Hapi = require('hapi');
const Inert = require('inert');
const h2o2 = require('h2o2');
const Wreck = require('wreck');
const good = require('good');
const _ = require('lodash');
const url = require('url');

const packageInfo = require('./package.json');

const baseForward = {
    protocol: getEnvParam('CONCOURSE_URL_PROTOCOL', 'https'),
    slashes: true,
    host: getEnvParam('CONCOURSE_URL_HOST')
};
const baseProxyOptions = {
    uri: url.format(_.extend({}, baseForward, {
        pathname: '{apipath}'
    })),
    passThrough: true
};

function doRedirect(request, reply) {
    var targetRedirect = _.extend({}, baseForward, {
        pathname: request.params.apipath,
        query: request.query
    });

    return reply.redirect(url.format(targetRedirect));
}

/**
 * Handler for environment requests. Takes JS_* environment variables from 
 * process.env and passes them back to the front end.
 */
function getEnv(request, reply) {
    var frontEndEnv = _.pickBy(process.env, (value, key) => {
        return _.startsWith(key, 'JS_');
    });

    // Extend with special env variables we need on both ends
    _.extend(frontEndEnv, {
        JS_PRIVILEGED_FILTER: getEnvParam('PRIVILEGED_FILTER')
    });

    _.extend(frontEndEnv, {
        package: _.pick(packageInfo, [
            'version'
        ])
    });

    reply(frontEndEnv);
}

function getResponseHeader(response, header, asArray) {
    var head = response.headers[header.toLowerCase()];
    var headParts = _.map(head.split(/;\s*/), (str) => {
        if (str.includes('=')){
            return str.split(/\s*=\s*/);
        } else {
            return str;
        }
    });
    if (_.isNil(asArray) || asArray === false) {
        headParts = headParts[0];
    }
    return headParts;
}

function handleConcoursePublic(err, res, request, reply, settings, ttl) {
    Wreck.read(res, {}, function (err, payload) {
        var body = payload;

        var contentType = getResponseHeader(res, 'content-type', true);
        if (contentType[0] === 'text/css') {
            body = payload.toString(contentType[1].encoding);
            body = body.replace(/\/public\//g, '/c/public/');
        }

        reply(body).headers = res.headers;
    });
}

function getEnvParam(name, defaultValue) {
    var value = process.env[name];
    if (!_.isNil(value)) {
        if (_.indexOf(value, '{') === 0 || _.indexOf(value, '[') === 0) {
            value = JSON.parse(value);
        }
        return value;
    }
    return defaultValue;
}

function getBasicAuthHeaders() {
    var basicAuth = getEnvParam('CONCOURSE_BASIC_AUTH');
    if (_.isNil(basicAuth)) {
        var message = 'Privileged access is disabled.';
        var err = new Error(message);
        err.httpCode = 401;
        err.httpMessage = message;
        throw err;
    }
    return {
        Authorization: 'Basic ' + new Buffer(basicAuth.username + ':' + basicAuth.password).toString('base64')
    };
}

function login() {
    return new Promise((resolve, reject) => {
        var loginUrl = _.extend({}, baseForward, {
            pathname: '/api/v1/teams/main/auth/token'
        });

        Wreck.get(url.format(loginUrl), {
            headers: getBasicAuthHeaders(),
            json: true
        }, (err, response, payload) => {
            if (err) {
                reject(err);
            } else if (response.statusCode !== 200) {
                reject(new Error('login returned status: ' + response.statusCode));
            } else {
                resolve(payload);
            }
        });
    });
}

function handlePrivileged(request, reply) {
    var filter = getEnvParam('PRIVILEGED_FILTER');
    if (filter) {
        var regex = new RegExp('api/v1/teams/([^/]+)/pipelines/([^/]+)/jobs/([^/]+)/([^/]+)$');
        var matches = regex.exec(request.params.apipath);
        //var teamName = matches[1];
        var pipelineName = matches[2];
        var jobName = matches[3];
        var action = _.get({
            builds: 'trigger',
            pause: 'pause',
            unpause: 'pause'
        }, matches[4]);

        // check that the "path" is "allowed"
        var allowed = _.get(filter, [pipelineName, jobName, action]);
        if (!allowed) {
            reply('forbidden by administrator').code(403);
            return;
        }
    }

    login().then((loginResponse) => {
        request.headers.Authorization = `${loginResponse.type} ${loginResponse.value}`;
        reply.proxy(baseProxyOptions);
    }).catch((err) => {
        console.error(err);
        reply(err.httpMessage || 'login failed').code(err.httpCode || 500);
    });
}


var config = {
    debug: {
        request: ['error', 'database', 'read']
    }
};
var server = new Hapi.Server(config);
server.connection({ port: 8888 });

server.register([
    h2o2,
    Inert,
    {
        register: good,
        options: {
            reporters: {
                myConsoleReporter: [{
                    module: 'good-squeeze',
                    name: 'Squeeze',
                    args: [{ log: '*', response: '*' }]
                }, {
                    module: 'good-console'
                }, 'stdout']
            }
        }
    }
    ], (err) => {

    if (err) {
        console.error('Failed loading plugins');
        process.exit(1);
    }

    server.route({
        method: 'GET',
        path: '/{param*}',
        handler: {
            directory: {
                path: 'public'
            }
        }
    });

    server.route({
        method: 'GET',
        path: '/search/{pattern*}',
        handler: {
            file: 'public/index.html'
        }
    });

    server.route({
        method: 'GET',
        path: '/team/{team}',
        handler: {
            file: 'public/index.html'
        }
    });

    server.route({
        method: 'GET',
        path: '/team/{team}/search/{pattern*}',
        handler: {
            file: 'public/index.html'
        }
    });

    server.route({
        method: 'GET',
        path: '/e',
        config: {
            handler: getEnv
        }
    });

    server.route({
        method: 'GET',
        path: '/c/{apipath*}',
        handler: {
            proxy: baseProxyOptions
        }
    });

    server.route({
        method: 'GET',
        path: '/c/public/{publicpath*}',
        handler: {
            proxy: {
                uri: url.format(_.extend({}, baseForward, {
                    pathname: '/public/{publicpath}'
                })),
                onResponse: handleConcoursePublic
            }
        }
    });

    server.route({
        method: 'GET',
        path: '/favicon.ico',
        handler: {
            proxy: {
                uri: url.format(_.extend({}, baseForward, {
                    pathname: '/favicon.ico'
                }))
            }
        }
    });

    server.route({
        method: 'GET',
        path: '/r/{apipath*}',
        config: {
            handler: doRedirect
        }
    });

    server.route({
        method: ['POST', 'PUT'],
        path: '/c/privileged/{apipath*}',
        config: {
            handler: handlePrivileged,
            payload: {
                output: 'stream',
                parse: false
            }
        }
    });

    server.start(() => {
        console.log('Server running at:', server.info.uri);
    });
});


