/*
 * Copyright 2015 Apereo Foundation (AF) Licensed under the
 * Educational Community 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://opensource.org/licenses/ECL-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.
 */

var _ = require('underscore');
var assert = require('assert');

var RestAPI = require('oae-rest');
var TestsUtil = require('oae-tests');

var AuthenticationAPI = require('oae-authentication');

describe('Authentication', function() {

    // Rest context that can be used every time we need to make a request as a tenant admin
    var camAdminRestContext = null;
    // Rest context that can be used every time we need to make a request as a global admin
    var globalAdminRestContext = null;

    /**
     * Function that will fill up the tenant admin and anymous rest context
     */
    before(function(callback) {
        // Prepare the contexts with which we'll perform requests
        camAdminRestContext = TestsUtil.createTenantAdminRestContext(global.oaeTests.tenants.cam.host);
        globalAdminRestContext = TestsUtil.createGlobalAdminRestContext();

        return callback();
    });

    describe('Local authentication', function() {

        /*!
         * Given a set of user agents, perform an authentication using each and record the cookie
         * data that is returned in the authentication response for each user agent
         *
         * @param  {RestContext}    restContext                 The REST context to use for each authentication
         * @param  {String}         username                    The username to use for each authentication
         * @param  {String}         password                    The password to use for each authentication
         * @param  {String[]}       userAgents                  A list of user agents to authenticate with
         * @param  {Function}       callback                    Invoked when all authentications have successfully completed
         * @param  {Object}         callback.userAgentCookies   An object whose keys are the user agents, and values are an array of `request` Cookie's that were returned in the authentication response
         */
        var _getCookiesForUserAgents = function(restContext, username, password, userAgents, callback, _userAgentCookiesToGet, _userAgentCookies) {
            _userAgentCookiesToGet = _userAgentCookiesToGet || userAgents.slice();
            _userAgentCookies = _userAgentCookies || {};

            // If there are no more user agents to authenticate with, return with the cookies we have
            if (_.isEmpty(_userAgentCookiesToGet)) {
                return callback(_userAgentCookies);
            }

            // Get the next user agent to authenticate, and apply the user-agent header to the
            // restContext
            var userAgent = _userAgentCookiesToGet.shift();
            restContext.additionalHeaders = restContext.additionalHeaders || {};
            restContext.additionalHeaders['user-agent'] = userAgent;

            // First logout the context to clear the cookie jar
            RestAPI.Authentication.logout(restContext, function(err) {
                assert.ok(!err);

                // Authenticate the user agent
                RestAPI.Authentication.login(restContext, username, password, function(err) {
                    assert.ok(!err);

                    // Aggregate the cookies into the user agent map
                    _userAgentCookies[userAgent] = restContext.cookieJar._jar.getCookiesSync(restContext.host);

                    return _getCookiesForUserAgents(restContext, username, password, userAgents, callback, _userAgentCookiesToGet, _userAgentCookies);
                });
            });
        };

        /**
         * Test that verifies that cookies have an appropriate expiration heuristic depending on the
         * client device and tenant
         */
        it('verify cookie expiration for mobile and non-mobile browsers', function(callback) {

            /*!
             * A collection of user agents for a variety of desktop / non-mobile clients
             */
            var nonMobileUserAgents = [
                // Firefox variants
                'Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0',
                'Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0',
                'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0',
                'Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0',

                // Chrome variants
                'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36',
                'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36',
                'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36',

                // Safari variants
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2',
                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10',

                // IE variants
                'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko',
                'Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko',
                'Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0',
                'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)',
                'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',
                'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
                'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/4.0; InfoPath.2; SV1; .NET CLR 2.0.50727; WOW64)',
                'Mozilla/5.0 (compatible; MSIE 10.0; Macintosh; Intel Mac OS X 10_7_3; Trident/6.0)',
                'Mozilla/4.0 (Compatible; MSIE 8.0; Windows NT 5.2; Trident/6.0)',
                'Mozilla/4.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
                'Mozilla/1.22 (compatible; MSIE 10.0; Windows 3.1)'
            ];

            /*!
             * A collection of user agents for mobile devices (phones, tablets, etc...)
             */
            var mobileUserAgents = [
                // iPhone/iPad variants
                'Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7',
                'Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
                'Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
                'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25',
                'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25',

                // Android variants
                'Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
                'Mozilla/5.0 (Linux; U; Android 4.0.3; de-ch; HTC Sensation Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
                'Mozilla/5.0 (Linux; U; Android 2.3.5; zh-cn; HTC_IncredibleS_S710e Build/GRJ90) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1',

                // Windows variants
                'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)',
                'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0)',
                'HTC_Touch_3G Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 7.11)',
                'Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; Nokia;N70)'
            ];

            // Combine all user agents
            var allUserAgents = _.chain(mobileUserAgents).union(nonMobileUserAgents).shuffle().value();

            // Create a test user
            var username = TestsUtil.generateTestUserId();
            var email = TestsUtil.generateTestEmailAddress(null, global.oaeTests.tenants.cam.emailDomain);
            RestAPI.User.createUser(camAdminRestContext, username, 'password', 'Test User', email, {}, function(err, createdUser) {
                assert.ok(!err);
                var userRestContext = TestsUtil.createTenantRestContext(global.oaeTests.tenants.cam.host, username, 'password');

                // Get all user agents for a user. When using a user tenant, mobile user-agents
                // should result in a cookie that has a length expiry
                _getCookiesForUserAgents(userRestContext, username, 'password', allUserAgents, function(userAgentCookies) {
                    assert.strictEqual(_.keys(userAgentCookies).length, allUserAgents.length);

                    // Ensure each mobile user agent has a cookie with an explicit expiry time that
                    // is more than 29 days into the future
                    _.each(mobileUserAgents, function(mobileUserAgent) {
                        var cookies = userAgentCookies[mobileUserAgent];

                        assert.strictEqual(cookies.length, 2);
                        _.each(cookies, function(cookie) {
                            assert.ok(_.isNumber(cookie.TTL()));
                            assert.ok(cookie.TTL() > (1000 * 60 * 60 * 24 * 29));
                            assert.notEqual(cookie.TTL(), Infinity);
                        });
                    });

                    // Ensure each non-mobile user agent has a cookie without an explicit expiry
                    // (i.e., browser session cookie)
                    _.each(nonMobileUserAgents, function(nonMobileUserAgent) {
                        var cookies = userAgentCookies[nonMobileUserAgent];

                        assert.strictEqual(cookies.length, 2);
                        _.each(cookies, function(cookie) {
                            assert.strictEqual(cookie.TTL(), Infinity);
                        });
                    });

                    // Get all user agents for a global admin login. When using the global admin
                    // tenant, both mobile and non-mobile user-agents should not have an extended
                    // expiry
                    _getCookiesForUserAgents(globalAdminRestContext, 'administrator', 'administrator', allUserAgents, function(userAgentCookies) {
                        assert.strictEqual(_.keys(userAgentCookies).length, allUserAgents.length);

                        // Ensure all user agents have a cookie without an explicit expiry (i.e.,
                        // browser session cookie)
                        _.each(allUserAgents, function(userAgent) {
                            var cookies = userAgentCookies[userAgent];

                            assert.ok(!_.isEmpty(cookies));
                            _.each(cookies, function(cookie) {
                                assert.strictEqual(cookie.TTL(), Infinity);
                            });
                        });

                        return callback();
                    });
                });
            });
        });
    });
});
