'use strict';

/**
 * @ngdoc function
 * @name yololiumApp.controller:OauthCtrl
 * @description
 * # OauthCtrl
 * Controller of the yololiumApp
 */
angular.module('yololiumApp')
  .controller('AuthorizationDialogController', [
    '$window'
  , '$location'
  , '$stateParams'
  , '$q'
  , '$timeout'
  , '$scope'
  , '$http'
  , 'DaplieApiConfig'
  , 'DaplieApiSession'
  , 'DaplieApiRequest'
  , function (
      $window
    , $location
    , $stateParams
    , $q
    , $timeout
    , $scope
    , $http
    , LdsApiConfig
    , LdsApiSession
    , LdsApiRequest
    ) {

    var scope = this;

    function isIframe () {
      try {
        return window.self !== window.top;
      } catch (e) {
        return true;
      }
    }

    // TODO move into config
    var scopeMessages = {
      directories: "View directories"
    , me: "View your own Account"
    , '*': "Use the Full Developer API"
    };

    function updateAccepted() {
      scope.acceptedString = scope.pendingScope.filter(function (obj) {
        return obj.acceptable && obj.accepted;
      }).map(function (obj) {
        return obj.value;
      }).join(' ');

      return scope.acceptedString;
    }

    function scopeStrToObj(value, accepted) {
      // TODO parse subresource (dns:example.com:cname)
      return {
        accepted: accepted
      , acceptable: !!scopeMessages[value]
      , name: scopeMessages[value] || 'Invalid Scope \'' + value + '\''
      , value: value
      };
    }

    function requestSelectedAccount(account, query, origin) {
      // TODO Desired Process
      // * check locally
      // * if permissions pass, sign a jwt and post to server
      // * if permissions fail, get from server (posting public key), then sign jwt
      // * redirect to authorization_code_callback?code= or oauth3.html#token=
      return $http.get(
        LdsApiConfig.providerUri + '/api/org.oauth3.accounts/:account_id/grants/:client_id'
          .replace(/:account_id/g, account.accountId)
          .replace(/:client_id/g, query.client_id)
      , { headers: { Authorization: "Bearer " + account.token } }
      ).then(function (resp) {
        var err;

        if (!resp.data) {
          err = new Error("[Uknown Error] got no response (not even an error)");
          console.error(err.stack);
          throw err;
        }

        if (resp.data.error) {
          console.error('[authorization-dialog] resp.data');
          err = new Error(resp.data.error.message || resp.data.error_description);
          console.error(err.stack);
          scope.error = resp.data.error;
          scope.rawResponse = resp.data;
          return $q.reject(err);
        }

        return resp.data;
      });
    }

    scope.chooseAccount = function (/*profile*/) {
      $window.alert("user switching not yet implemented");
    };
    scope.updateScope = function () {
      updateAccepted();
    };

    function parseScope(scope) {
      return (scope||'').split(/[\s,]/g)
    }
    function getNewPermissions(grant, query) {
      var grantedArr = parseScope(grant.scope);
      var requestedArr = parseScope(query.scope||'');

      return requestedArr.filter(function (scope) {
        return -1 === grantedArr.indexOf(scope);
      });
    }

    function generateToken(account, grant, query) {
      var err = new Error("generateToken not yet implemented");
      throw err;
    }

    function generateCode(account, grant, query) {
      var err = new Error("generateCode not yet implemented");
      throw err;
    }

    function getAccountPermissions(account, query, origin) {
      return requestSelectedAccount(account, query, origin).then(function (grants) {
        var grant = grants[query.client_id] || grants;
        var grantedArr = parseScope(grant.scope);
        var pendingArr = getNewPermissions(grant, query);

        var grantedObj = grantedArr.map(scopeStrToObj);
        // '!' is a debug scope that ensures the permission dialog will be activated
        // also could be used for switch user
        var pendingObj = pendingArr.filter(function (v) { return '!' !== v; }).map(scopeStrToObj);

        scope.client = grant.client;

        if (!scope.client.title) {
          scope.client.title = scope.client.name || 'Missing App Title';
        }

        scope.selectedAccountId = account.accountId;

        if (!checkRedirect(grant, query)) {
          location.href = 'https://oauth3.org/docs/errors#E_REDIRECT_ATTACK';
          return;
        }

        // key generation in browser
        // possible iframe vulns?
        if (pendingArr.length) {
          if (scope.iframe) {
            location.href = query.redirect_uri + '#error=access_denied&error_description='
              + encodeURIComponent("You're requesting permission in an iframe, but the permissions have not yet been granted")
              + '&error_uri=' + encodeURIComponent('https://oauth3.org/docs/errors/#E_IFRAME_DENIED');
            return;
          }

          updateAccepted();
          return grant;
        }
        else if ('token' === query.response_type) {
          generateToken(account, grant, query).then(function (token) {
            location.href = query.redirect_uri + '#token=' + token;
          });
          return;
        }
        else if ('code' === query.response_type) {
          // NOTE
          // A client secret may never be exposed in a client
          // A code always requires a secret
          // Therefore this redirect_uri will always be to a server, not a local page
          generateCode(account, grant, query).then(function () {
            location.href = query.redirect_uri + '?code=' + code;
          });
          return;
        } else {
          location.href = query.redirect_uri + '#error=E_UNKNOWN_RESPONSE_TYPE&error_description='
            + encodeURIComponent("The '?response_type=' parameter must be set to either 'token' or 'code'.")
            + '&error_uri=' + encodeURIComponent('https://oauth3.org/docs/errors/#E_UNKNOWN_RESPONSE_TYPE');
          return;
        }
      });
    }

    function redirectToFailure() {
      var redirectUri = $location.search().redirect_uri;

      var parser = document.createElement('a');
      parser.href = redirectUri;
      if (parser.search) {
        parser.search += '&';
      } else {
        parser.search += '?';
      }
      parser.search += 'error=E_NO_SESSION';
      redirectUri = parser.href;

      window.location.href = redirectUri;
    }

    function initAccount(session, query, origin) {
      return LdsApiRequest.getAccountSummaries(session).then(function (accounts) {
        var account = LdsApiSession.selectAccount(session);
        var profile;

        scope.accounts = accounts.map(function (account) {
          return account.profile.me;
        });
        accounts.some(function (a) {
          if (LdsApiSession.getId(a) === LdsApiSession.getId(account)) {
            profile = a.profile;
            a.selected = true;
            return true;
          }
        });

        if (profile.me.photos[0]) {
          if (!profile.me.photos[0].appScopedId) {
            // TODO fix API to ensure corrent id
            profile.me.photos[0].appScopedId = profile.me.appScopedId || profile.me.app_scoped_id;
          }
        }
        profile.me.photo = profile.me.photos[0] && LdsApiRequest.photoUrl(account, profile.me.photos[0], 'medium');
        scope.account = profile.me;

        scope.token = $stateParams.token;

        /*
        scope.accounts.push({
          displayName: 'Login as a different user'
        , new: true
        });
        */

        //return determinePermissions(session, account);
        return getAccountPermissions(account, query, origin).then(function () {
          // do nothing?
          scope.selectedAccount = session; //.account;
          scope.previousAccount = session; //.account;
          scope.updateScope();
        }, function (err) {
          if (/logged in/.test(err.message)) {
            return LdsApiSession.destroy().then(function () {
              init();
            });
          }

          if ('E_INVALID_TRANSACTION' === err.code) {
            window.alert(err.message);
            return;
          }

          console.warn("[ldsconnect.org] [authorization-dialog] ERROR somewhere in oauth process");
          console.warn(err);
          window.alert(err.message);
        });
      });
    }

    function init() {
      scope.iframe = isIframe();
      var query = $location.search();
      var referrer = $window.document.referer || $window.document.origin;
      // TODO XXX this should be drawn from site-specific config
      var apiHost = 'https://oauth3.org';

      // if the client didn't specify an id the client is the referrer
      if (!query.client_id) {
        // if we were redirect here by our own apiHost we can trust the host as the client_id
        // (and it will be checked against allowed urls anyway)
        if (referrer === apiHost) {
          query.client_id = ('https://' + query.host);
        } else {
          query.client_id = referrer;
        }
      }

      // TODO XXX to allow or to disallow mounted apps, that is the question
      // https://example.com/blah/ -> example.com/blah
      query.client_id = query.client_id.replace(/^https?:\/\//i, '').replace(/\/$/, '');

      if (scope.iframe) {
        return LdsApiSession.checkSession().then(function (session) {
          if (session.accounts.length) {
            // TODO make sure this fails / notifies
            return initAccount(session, query, origin);
          } else {
            // TODO also notify to bring to front
            redirectToFailure();
          }
        });
      }

      // session means both login(s) and account(s)
      return LdsApiSession.requireSession(
        // role
        null
        // TODO login opts (these are hypothetical)
      , { close: false
        , options: ['login', 'create']
        , default: 'login'
        }
        // TODO account opts
      , { verify: ['email', 'phone']
        }
      , { clientId: query.clientId
        }
      ).then(function (session) {
        initAccount(session, query, origin)
      });
    }

    init();

    // I couldn't figure out how to get angular to bubble the event
    // and the oauth2orize framework didn't seem to work with json form uploads
    // so I dropped down to quick'n'dirty jQuery to get it all to work
    scope.hackFormSubmit = function (opts) {
      scope.submitting = true;
      scope.cancelHack = !opts.allow;
      scope.authorizationDecisionUri = LdsApiConfig.providerUri + '/api/oauth3/authorization_decision';
      scope.updateScope();

      $window.jQuery('form.js-hack-hidden-form').attr('action', scope.authorizationDecisionUri);

      // give time for the apply to take place
      $timeout(function () {
        $window.jQuery('form.js-hack-hidden-form').submit();
      }, 50);
    };
    scope.allowHack = function () {
      scope.hackFormSubmit({ allow: true });
    };
    scope.rejectHack = function () {
      scope.hackFormSubmit({ allow: false });
    };
  }]);
