var _ = require('lodash'),
    sdk = require('postman-collection'),

    createAuthInterface = require('./auth-interface'),

    AUTH_TYPE_PROP = '__auth_type',

    AuthLoader,
    authorizeRequest;

/**
 * This object manages loading and finding Handlers for auth.
 *
 * @type AuthLoader
 */
AuthLoader = {
    /**
     * Houses list of available Authentication handlers.
     *
     * @property {Object}
     */
    handlers: {},

    /**
     * Finds the Handler for an Auth type.
     *
     * @param name
     *
     * @returns {AuthHandler}
     */
    getHandler: function (name) {
        return AuthLoader.handlers[name];
    },

    /**
     * Adds a Handler for use with given Auth type.
     *
     * @param Handler
     * @param name
     */
    addHandler: function (Handler, name) {
        if (!_.isFunction(Handler.init)) {
            throw new Error('The handler for "' + name + '" does not have an "init" function, which is necessary');
        }

        if (!_.isFunction(Handler.pre)) {
            throw new Error('The handler for "' + name + '" does not have a "pre" function, which is necessary');
        }

        if (!_.isFunction(Handler.post)) {
            throw new Error('The handler for "' + name + '" does not have a "post" function, which is necessary');
        }

        if (!_.isFunction(Handler.sign)) {
            throw new Error('The handler for "' + name + '" does not have a "sign" function, which is necessary');
        }

        Object.defineProperty(Handler, AUTH_TYPE_PROP, {
            value: name,
            configurable: false,
            enumerable: false,
            writable: false
        });

        AuthLoader.handlers[name] = Handler;
    },

    /**
     * Removes the Handler for the Auth type.
     *
     * @param name
     */
    removeHandler: function (name) {
        AuthLoader.handlers[name] && (delete AuthLoader.handlers[name]);
    }
};

// Create a Handler from each Signer that the SDK provides. Basically, we augment the signers with extra
// helper functions which take over the job of preparing a request for signing.
_.forEach({
    noauth: require('./noauth'),
    awsv4: require('./aws4'),
    basic: require('./basic'),
    bearer: require('./bearer'),
    digest: require('./digest'),
    hawk: require('./hawk'),
    oauth1: require('./oauth1'),
    oauth2: require('./oauth2'),
    ntlm: require('./ntlm'),
    apikey: require('./apikey'),
    edgegrid: require('./edgegrid')
}, AuthLoader.addHandler);

/**
 * Creates a copy of request, with the appropriate auth headers or parameters added.
 *
 * @note This function does not take care of resolving variables.
 *
 * @param {Request} request
 * @param done
 *
 * @returns {Request}
 */
authorizeRequest = function (request, done) {
    if (!request.auth) {
        return done();
    }

    var clonedReq = new sdk.Request(request.toJSON()),
        auth = clonedReq.auth,
        authInterface = createAuthInterface(auth),
        handler = AuthLoader.getHandler(auth.type);

    if (handler) {
        handler.sign(authInterface, clonedReq, function () { return done(null, clonedReq); });
    }
    else {
        return done(new Error('runtime~authorizeRequest: could not find handler for auth type ' + auth.type));
    }
};

module.exports = {
    AuthLoader: AuthLoader,
    authorizeRequest: authorizeRequest
};

// Interface
/**
 * Interface for implementing auth handlers
 *
 * @interface AuthHandlerInterface
 */

// Interface functions
/**
 * Defines the behaviour of an Auth Handler. This way the handler allows to statically analyse
 * any changes the Handler will make ahead of time.
 *
 * @member {AuthHandlerInterface~AuthManifest} AuthHandlerInterface#manifest
 */

/**
 * This hook decides whether all the required parameters are present in the auth or not.
 * What happens next is dependent upon how the `done` callback is called.
 * Check {@link AuthHandlerInterface~authPreHookCallback} for all the possible ways the callback can be called.
 *
 * @function
 * @name AuthHandlerInterface#pre
 *
 * @param {AuthInterface} auth
 * @param {AuthHandlerInterface~authPreHookCallback} done
 * Callback function which takes error, success, and request as arguments
 */

/**
 * This hook is called with the response from the intermediate request, which was requested from the
 * [pre]{@link AuthHandlerInterface#pre} hook.
 * Here the `auth` can be modified using the response. After this [pre]{@link AuthHandlerInterface#pre} hook will be
 * called again to verify the required parameters.
 *
 * @function
 * @name AuthHandlerInterface#init
 *
 * @param {AuthInterface} auth
 * @param {Response} response
 * @param {AuthHandlerInterface~authInitHookCallback} done Callback function which takes error as the only argument
 */

/**
 * This hook signs the `request` using the `auth`.
 *
 * @function
 * @name AuthHandlerInterface#sign
 *
 * @param {AuthInterface} auth
 * @param {Request} request
 * @param {AuthHandlerInterface~authSignHookCallback} done Callback function which takes error as the only argument
 */

/**
 * This hook is called after the request is made. It receives the response using which it can determine whether
 * it was a failure or success. It can also modify the `auth` and ask to replay the `request`.
 * For this it has to call the [done]{@link AuthHandlerInterface~authPostHookCallback} callback with `success` as false.
 *
 * @function
 * @name AuthHandlerInterface#post
 *
 * @param {AuthInterface} auth
 * @param {Response} response
 * @param {AuthHandlerInterface~authPostHookCallback} done Callback function which takes error and success as arguments
 */


// Callbacks
/**
 * This callback is called in the `pre` hook of the auth handler
 * Depending on what parameters are passed in this callback, one of the following flows will be executed:
 * 1. return (err): The request will be stopped and the error will be bubbled up
 * 2. return (null, true): The request will be signed and sent
 * 3. return (null, false): The request will be sent without being signed
 * 4. return (null, false, `request`):
 *  - send the intermediate request
 *  - invoke the auth's [init]{@link AuthHandlerInterface#init} hook with the response of the intermediate request
 *  - invoke the auth's [pre]{@link AuthHandlerInterface#pre} hook
 * @callback AuthHandlerInterface~authPreHookCallback
 * @param {?Error} err
 * @param {Boolean} success Defines whether the [pre]{@link AuthHandlerInterface#pre} hook was successful.
 * @param {Request~definition|String} [request] It can be either request definition or request URL
 */

/**
 * This callback is called in the `init` hook of the auth handler
 * @callback AuthHandlerInterface~authInitHookCallback
 * @param {?Error} err
 */

/**
 * This callback is called in the `sign` hook of the auth handler
 * @callback AuthHandlerInterface~authSignHookCallback
 * @param {?Error} err
 */

/**
 * This callback is called in the `post` hook of the auth handler
 * @callback AuthHandlerInterface~authPostHookCallback
 * @param {?Error} err
 * @param {Boolean} success Defines whether the request was successful or not. If not, it will be replayed.
 */

/**
 * Structure of an Auth Manifest. See {@link AuthHandlerInterface#manifest} for description.
 *
 * @typedef {Object} AuthHandlerInterface~AuthManifest
 *
 * @property {Object} info
 * @property {String} info.name
 * @property {String} info.version
 * @property {Array<Object>} updates
 */
