'use strict';

/**
 *
 * @param {PgConnection} pgConnection
 * @param metadataBackend
 * @param {MapStore} mapStore
 * @param {TemplateMaps} templateMaps
 * @constructor
 * @type {AuthBackend}
 */
function AuthBackend(pgConnection, metadataBackend, mapStore, templateMaps) {
    this.pgConnection = pgConnection;
    this.metadataBackend = metadataBackend;
    this.mapStore = mapStore;
    this.templateMaps = templateMaps;
}

module.exports = AuthBackend;

// Check if the user is authorized by a signer
//
// @param res express response object
// @param callback function(err, signed_by) signed_by will be
//                 null if the request is not signed by anyone
//                 or will be a string cartodb username otherwise.
//
AuthBackend.prototype.authorizedBySigner = function(req, res, callback) {
    if ( ! res.locals.token || ! res.locals.signer ) {
        return callback(null, false); // no signer requested
    }

    var self = this;

    var layergroup_id = res.locals.token;
    var auth_token = req.query.auth_token;

    this.mapStore.load(layergroup_id, function(err, mapConfig) {
        if (err) {
            return callback(err);
        }

        var authorized = self.templateMaps.isAuthorized(mapConfig.obj().template, auth_token);

        return callback(null, authorized);
    });
};

function isValidApiKey(apikey) {
    return apikey.type &&
        apikey.user &&
        apikey.databasePassword &&
        apikey.databaseRole;
}

// Check if a request is authorized by api_key
//
// @param user
// @param res express response object
// @param callback function(err, authorized)
//        NOTE: authorized is expected to be 0 or 1 (integer)
//
AuthBackend.prototype.authorizedByAPIKey = function(user, res, callback) {
    const apikeyToken = res.locals.api_key;
    const basicAuthUsername = res.locals.basicAuthUsername;

    if ( ! apikeyToken ) {
        return callback(null, false); // no api key, no authorization...
    }

    this.metadataBackend.getApikey(user, apikeyToken, (err, apikey) => {
        if (err) {
            if (isNameNotFoundError(err)) {
                err.http_status = 404;
            }

            return callback(err);
        }

        if ( !isValidApiKey(apikey)) {
            const error = new Error('Unauthorized');
            error.type = 'auth';
            error.subtype = 'api-key-not-found';
            error.http_status = 401;

            return callback(error);
        }

        if (!usernameMatches(basicAuthUsername, res.locals.user)) {
            const error = new Error('Forbidden');
            error.type = 'auth';
            error.subtype = 'api-key-username-mismatch';
            error.http_status = 403;

            return callback(error);
        }

        if (!apikey.grantsMaps) {
            const error = new Error('Forbidden');
            error.type = 'auth';
            error.subtype = 'api-key-does-not-grant-access';
            error.http_status = 403;

            return callback(error);
        }

        return callback(null, true, apikey);
    });
};

function isNameNotFoundError (err) {
    return err.message && -1 !== err.message.indexOf('name not found');
}

function usernameMatches (basicAuthUsername, requestUsername) {
    return !(basicAuthUsername && (basicAuthUsername !== requestUsername));
}

/**
 * Check access authorization
 *
 * @param req - standard req object. Importantly contains table and host information
 * @param res - standard res object. Contains the auth parameters in locals
 * @param callback function(err, allowed) is access allowed not?
 */
AuthBackend.prototype.authorize = function(req, res, callback) {
    var user = res.locals.user;

    this.authorizedByAPIKey(user, res, (err, isAuthorizedByApikey) => {
        if (err) {
            return callback(err);
        }

        if (isAuthorizedByApikey) {
            return this.pgConnection.setDBAuth(user, res.locals, 'regular', function (err) {
                req.profiler.done('setDBAuth');

                if (err) {
                    return callback(err);
                }

                callback(null, true);
            });
        }

        this.authorizedBySigner(req, res, (err, isAuthorizedBySigner) => {
            if (err) {
                return callback(err);
            }

            if (isAuthorizedBySigner) {
                return this.pgConnection.setDBAuth(user, res.locals, 'master', function (err) {
                    req.profiler.done('setDBAuth');

                    if (err) {
                        return callback(err);
                    }

                    callback(null, true);
                });
            }

            // if no signer name was given, use default api key
            if (!res.locals.signer) {
                return this.pgConnection.setDBAuth(user, res.locals, 'default', function (err) {
                    req.profiler.done('setDBAuth');

                    if (err) {
                        return callback(err);
                    }

                    callback(null, true);
                });
            }

            // if signer name was given, return no authorization
            return callback(null, false);
        });
    });
};
