/**
 * Copyright 2015 Adrian Carballo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';
var querystring = require('querystring');
var request = require('request-promise');
var deepExtend = require('deep-extend');


const toObject = (body) => {
    try {return JSON.parse(body)}
    catch (e) {return querystring.parse(body)}
};

const OAuthClientError = {
    name: 'OAuthClientError',
    message: `Please specify the client_id and client_secret for your app. 
              Check your oauth provider for info on how to get your client id 
              and secret.`
};


/**
 * Creates a new OAuth2 envelope that can be used to authorize with a provider, 
 * and then make requests to the authorized resources.
 * 
 * @param  {String} authUrl        The auth url as specified by the provider.
 * @param  {String} accessTokenUrl The access token url as specified by the 
 *     provider.
 * @param  {String} client_id      Your client id issued by the provider.
 * @param  {String} client_secret  Your client secret issued by the provider.
 * @param  {Object} [opts={}]      Configurable options
 * @example
 *     {
 *         headers, // Custom headers that will be sent with each request.
 *         params,  // Custom params that will be sent to the authorize url and
 *                  // the access token url.
 *     }
 * @return {Object} The OAuth2 envelope.
 * @example
 *
 *     // Step 1: Create the envelope
 *     var oauthor = require('oauthor');
 *     var oauthEnvelope = oauthor(
 *         'https://provider.com/oauth',
 *         'https://provider.com/access_token',
 *         'yourclientid',
 *         'yourclientsecret'
 *     );
 *
 *     // Step 2: Perform authorize request and handle redirect
 *     var code = redirect(oauthEnvelope.authorizeUrl({redirect_uri: '...'}));
 *
 *     // Step 3: Get code from the redirect and exchange for access token.
 *     var accessToken = oauthEnvelope.requestAccessToken(code);
 *
 *     // Sign all subsequent requests to the provider.
 *     var client = oauthEnvelope.sign(accessToken);
 *
 *     // Use `client` now to make authorized requests.
 *     var profile = client({url: 'https://provider.com./me'}).then(user => ...)
 */
function oauth2(authUrl, accessTokenUrl, client_id, client_secret, opts) {
    if (!client_id || !client_secret) throw OAuthClientError;

    const defaults = {headers: {}, params: {}};
    const options = deepExtend(defaults, opts);

    /**
     * Creates the headers that will be sent in the request.
     * 
     * @private
     * @param  {String} [token] If specified, an Authorization token will be
     * created with the given value.
     * @return {Object} The headers object.
     */
    var createHeaders = (token) => {
        if (!token) return options.headers;
        return deepExtend(options.headers, {Authorization: 'Bearer ' + token});
    };

    /**
     * Creates the url that should be used for authorization, with the specified
     * parameters as query string.
     * 
     * @param  {Object} [params={}] Parameters to send in the query string.
     * @return {String} The url that can be used to perform authorization.
     */
    var authorizeUrl = (params) => {
        const query = deepExtend(options.params, {client_id}, params);
        return authUrl + '?' + querystring.stringify(query);
    };

    /**
     * Performs a request to the access token endpoint that was specified, with
     * the code received from the authorization step, and optional parameters.
     * 
     * @param  {String} code The code obtained in the authorization step.
     * @param  {Object} [params={}] Parameters that will be posted to the 
     * request.
     * @return {Object} The response from the provider.
     */
    var requestAccessToken = (code, params) => {
        var formData = deepExtend(
            options.params,
            {grant_type: 'authorization_code', client_id, client_secret, code},
            params
        );

        return request({
            method: 'POST', url: accessTokenUrl, headers: createHeaders(), 
            formData
        }).then(toObject)
    };

    /**
     * Specifies an access token that should be used to sign all subsequent 
     * requests. The return value is a function that can be used to make 
     * authorized requests. The function takes an object specifying the request 
     * options that will be passed to the `request` function.
     * @see {@link http://github.com/request/request#requestoptions-callback}
     * 
     * @func
     * @sig String -> ({k: v} -> Object)
     * @param  {String} accessToken The access token obtained from the provider.
     * @return {Function}
     * @example
     *
     *     // Sign all requests with `youraccesscode`.
     *     var client = oauth.sign('youraccesscode');
     *     
     *     // Suppose you have the endpoint `https://provider.com/me` that
     *     // returns your user profile when queried.
     *     client({url: 'https://provider.com/me'}).then((user) => ...)
     */
    var sign = (accessToken) => (opts) =>
        request(deepExtend({headers: createHeaders(accessToken)}, opts))
        .then(toObject);

    // Exposes oauthor api
    return {authorizeUrl, requestAccessToken, sign};
}


module.exports = oauth2;
