$(function () {
  'use strict';

  var version = '1';
  if (version !== window.localStorage.getItem('version')) {
    window.localStorage.clear();
    window.localStorage.setItem('version', version);
  }

  var OAUTH3 = window.OAUTH3;
  var CONFIG = {
    host: OAUTH3.clientUri(window.location)
  , directives: null // will be populated before the login button appears
  };
  var loc = window.location;
  var util = {};
  var email;
  var tpls = {
    scope: $('.js-scopes-container').html()
  };
  $('.js-scopes-container').html('');

  // TODO let query.parse do location.hash || location.search || location
  var clientParams = OAUTH3.query.parse(loc.hash || loc.search);
  if (/authorization_dialog/.test(window.location.href)) {
    // OAUTH3.lintClientParams(params, window)
    // OAUTH3.normalizeClientParams(params, window)
    if (clientParams.debug) {
      console.info("'debug' exists, debug mode enabled. :)");
    }
    if (-1 === [ 'token', 'code' ].indexOf(clientParams.response_type)) {
      window.alert("'response_type' must exist and be either 'token' (implicit flow) or 'code' (authorization flow)");
      return;
    }
    if (!clientParams.state || -1 !== [ 'undefined', 'null' ].indexOf(clientParams.state)) {
      // TODO check bits
      window.alert("'state' should exist as a crypto-random string with 128-bits of entropy (32 hex characters)");
      return;
    }
    if (!clientParams.client_id || -1 !== [ 'undefined', 'null' ].indexOf(clientParams.client_id)) {
      console.warn(
        "'client_id' should exist as the uri identifying the client,"
      + " such as example.com or example.com:8080/my-app or, well,"
      + "'" + OAUTH3.url.normalize(window.document.referrer) + "'"
      );
    }
    if (clientParams.client_uri && clientParams.client_uri !== clientParams.client_id) {
      console.warn("'client_id' should be used instead of 'client_uri'");
    }
    if (!(clientParams.client_id || clientParams.client_uri)) {
      window.alert("'client_id' must exist as the uri identifying the client");
      console.error("'client_id' must exist as the uri identifying the client");
      clientParams.client_id = clientParams.client_uri = OAUTH3.url.normalize(window.document.referrer);
    }
    if (!clientParams.redirect_uri) {
      clientParams.redirect_uri = OAUTH3.url.normalize(clientParams.client_uri)
        + "/.well-known/oauth3/callback.html'";
      window.alert("'redirect_uri' must exist and should point to '" + clientParams.redirect_uri + "'");
      console.error("'redirect_uri' must exist and should point to '" + clientParams.redirect_uri + "'");
    }
    if (clientParams.subject) {
      console.log('clientParams.subject: ', clientParams.subject);
    }
    clientParams.referrer = window.document.referrer;
  }

  function normalizeSession(session) {
    // TODO casing
    // TODO expiry calculation
    // TODO leave this up to OAUTH3
    session.provider_uri = session.provider_uri || CONFIG.host;
    session.client_uri = session.client_uri || CONFIG.host; // same as provider in this case
  }

  function getSession(providerUri) {
    return OAUTH3.hooks.session.get(providerUri).then(function (session) {
      if (session && session.access_token) {
        normalizeSession(session);
        return OAUTH3.PromiseA.resolve(session);
      }
      else {
        return OAUTH3.PromiseA.reject(new Error("no access_token in session"));
      }
    });
  }

  function getGrants(session) {
    var clientLogo = OAUTH3.url.normalize(clientParams.client_uri) // optional relative logo ?
      + '/.well-known/oauth3/logo-128x128.png'
      ;

    // TODO put in directives.json or similar
    var grantDescriptions = {
      // deprecated
      'oauth3_authn': "Basic secure authentication"
    , 'auth@oauth3.org': "Basic secure authentication"
    , 'profile@oauth3.org': "Basic profile information"
    , 'profile.email@oauth3.org': "Email address"
    , 'profile.phone@oauth3.org': "Phone number"
    , 'wallet': "Access to payments and subscriptions"
    , 'bucket': "Access to file storage"
    , 'db': "Access to app data"
    , 'domains': "Domain registration (and Glue and NS records)" // TODO make an alias
    , 'domains@oauth3.org': "Domain registration (and Glue and NS records)" // TODO make an alias
    , 'domains:glue': "Glue Record management (for vanity nameservers)"
    , 'domains:ns': "Name Server management"
    , 'dns': "DNS records (A/AAAA, TXT, SRV, MX, etc)"

      // new
    , 'hello@example.com': "Hello World Example Access"
    , 'authn@oauth3.org': "Basic secure authentication"
    , 'wallet@oauth3.org': "Access to payments and subscriptions"
    , 'bucket@oauth3.org': "Access to file storage"
    , 'db@oauth3.org': "Access to app data"
    , 'domains@oauth3.org': "Domain registration (and Glue and NS records)" // TODO make an alias
    , 'domains:glue@oauth3.org': "Glue Record management (for vanity nameservers)"
    , 'domains:ns@oauth3.org': "Name Server management"
    , 'dns@oauth3.org': "DNS records (A/AAAA, TXT, SRV, MX, etc)"
    , 'www@daplie.com': "Websites and webapps"

    , '*': "FULL ACCOUNT ACCESS"
    };

    if ('authn@oauth3.org' === (clientParams.scope||'').toString()) {
      // implicit ppid grant is automatic
      console.warn('[security] fix scope checking on backend so that we can do automatic grants');
      // TODO check user preference if implicit ppid grant is allowed
      //return generateToken(session, clientObj);
    }

    $('.js-client-logo').attr('src', clientLogo);
    //$('.js-user-avatar').attr('src', userAvatar);

    return OAUTH3.authz.scopes(CONFIG.host, session, clientParams).then(function (scopes) {
      if (!scopes.pending.length) {
        // looks like we've done all of this before
        OAUTH3.authz.redirectWithToken(CONFIG.host, session, clientParams, scopes);
        return;
      }

      // This is to prevent click-jacking
      // TODO secure iFrame from click-jacking by requiring input?
      // ex: input.security-code[type="text"].val(Math.random()); input.js-verify-code[placeholder="Type what you see"]
      if (OAUTH3._browser.isIframe()) {
        location.href = clientParams.redirect_uri +'#'+ OAUTH3.query.stringify({
          state: clientParams.state
        , 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;
      }

      // TODO handle special scopes
      // ! always show permission dialog
      // ^ switch user
      // @n require actual login if not within n seconds
      // * account takeover
      scopes.pending.forEach(function (scope) {
        var $scope = $(tpls.scope);

        $scope.find('.js-scope-toggle').attr('name', scope);
        $scope.find('.js-scope-toggle').prop('checked', true);
        if (-1 !== scopes.granted.indexOf(scope)) {
          $scope.find('.js-scope-toggle').prop('disabled', true);
        }

        // the front-end recognizes the scope as valid
        // TODO list scopes in directive
        if (grantDescriptions[scope]) {
          $scope.find('.js-scope-desc').text(grantDescriptions[scope]);
        }
        else {
          //This disables the check/checkbox when we have an unrecognized grant.
          //This is disabled for testing until we can discover grants automatically.
          //TODO: Enable this when grants are discoverable
          //TODO: Indicate to user that this is disabled, not just unchecked.
          //$scope.find('.js-scope-toggle').prop('checked', false);
          //$scope.find('.check').attr("src", "./img/unpressed-check.png");
          //$scope.find('.js-scope-toggle').prop('disabled', true);
          $scope.find('.js-scope-desc').text(scope);
        }

        $('.js-scopes-container').append($scope);
      });

      $('.js-authz').show().addClass('in');
    });
  }

  util.checkAuthEmail = function (ev) {
    ev.preventDefault();
    ev.stopPropagation();

    var email = $(this).val();

    // wizarding - email detection
    if (/gmail|yahoo|msn|live/.test(email)) {
      $('.js-provider-logo').attr('src', 'img/not-provider.png');
    }
    else {
      $('.js-provider-logo').attr('src', 'img/daplie-provider.jpeg');
    }

    // TODO debounce 150ms
    // TODO test email by mx record
    if (/.+@.+\..+/.test(email)) {
      $('.js-authn-show').removeAttr('disabled');
      $('.js-oauth3-email').val(email);
    }
    else {
      $('.js-authn-show').prop('disabled', true);
    }
  };
  util.submitAuthEmail = function (ev) {
    ev.preventDefault();
    ev.stopPropagation();

    $('.js-authn-show').prop('disabled', true);
    // TODO loading

    email = $('.js-oauth3-email').val();
    return OAUTH3.authn.otp(CONFIG.directives, {email: email, mock: true}).then(function (otpResults) {

      if (otpResults.data.error) {
        window.alert('otpResults: ' + otpResults.data.error_description || otpResults.data.error.message);
        return;
      }

      var ua = window.navigator.userAgent;
      $('.js-sniffed-device').text(ua);
      $('.js-userid-container').removeClass('in').hide();
      $('.js-authn').show().addClass('in');
      $('.js-authn-otp-uuid').val(otpResults.data.uuid);

      $('.js-user-email').text(email);
    });
  };

  // Reference Implementation
  util.submitLoginCode = function (ev) {
    ev.preventDefault();
    ev.stopPropagation();

    // TODO
    // perhaps we should check that the code is valid before continuing to login (so that we don't send the key)

    // TODO
    // we should be sending the public key for this device as a jwk along with the authentication
    // (and how long to remember this device)
    var uuid = $('.js-authn-otp-uuid').val();
    var code = $('.js-authn-otp-code').val().trim();
    return OAUTH3.authn.resourceOwnerPassword(CONFIG.directives, {
      // TODO replace with signed hosted file
      client_agree_tos: 'oauth3.org/tos/draft'
    , client_id: CONFIG.host
    , client_uri: CONFIG.host
    , username: email
    , password: undefined
    , otp_code: code
      // TODO should be otp_id (agnostic of uuid)
    , otp_uuid: uuid
      // add expiration to the refresh token and/or public key
    , remember_device: $('.js-remember-label').find('.js-remember-checkbox').prop('checked')
    , mock: true
    }).then(function (session) {
      $('.js-authn').removeClass('in').hide();
      if (session.token.sub) {
        return OAUTH3.PromiseA.resolve(session);
      }

      return OAUTH3.requests.accounts.create(CONFIG.directives, session, {
        display_name: email.replace(/@.*/, '')
      , comment: "created for '" + email + "' by '" + CONFIG.host + "'"
      , priority: 1000          // default priority for first account
      , name: undefined         // TODO we could ask in the UI
      }).then(function (resp) {
        var results = resp.data;
        return OAUTH3.hooks.session.refresh(session, {
          access_token: (results.access_token || results.accessToken)
        , refresh_token: (results.refresh_token || results.refreshToken)
        });
      });
    }).then(function (session) {
      return getGrants(session).catch(function (err) {
        window.alert('grantResults: ' + err.message);
        console.error('scope results', err);
      });
    }, function (error) {
      console.error(error);
      $('.error-msg').text('Incorrect code');
    });
  };
  util.acceptScopesAndLogin = function (ev) {
    ev.preventDefault();
    ev.stopPropagation();

    // TODO choose from the selected accepted scopes
    var acceptedScopes = [];

    $('form.js-authorization-decision').find('input[type=checkbox]').each(function (i, el) {
      var $input = $(el);
      if ($input.prop('checked')/* && !$input.prop('disabled')*/) {
        acceptedScopes.push($input.attr('name'));
      }
    });

    getSession(CONFIG.host).then(function (session) {
      return OAUTH3.authz.scopes(CONFIG.host, session, clientParams).then(function (scopes) {
        scopes.new = acceptedScopes;
        return OAUTH3.authz.redirectWithToken(CONFIG.host, session, clientParams, scopes);
      });
    }, function (err) {
      console.error("Accept Scopes and Login");
      console.error(err);
    });
  };
  util.closeLoginDeny = function (ev) {
    ev.preventDefault();
    ev.stopPropagation();

    var denyObj = {
      error: 'access_denied'
    , error_description: 'The user has denied access.'
    , error_uri: 'https://' + CONFIG.host + '/.well-known/oauth3/errors.html#/?error=access_denied'
    , state: clientParams.state
    , scope: clientParams.scope
    };

    window.location = clientParams.redirect_uri + '#' + OAUTH3.query.stringify(denyObj);
  };
  util.handleLogout = function () {
    localStorage.clear();

    clientParams.redirect_uri += '?' + OAUTH3.query.stringify({
      state: clientParams.state
    , debug: clientParams.debug
    });

    window.location = OAUTH3.url.resolve(clientParams.client_uri, clientParams.redirect_uri);
  };
  util.editEmail = function () {
    $('.js-authn').hide();
    $('.js-userid-container').show();
  };


  //
  // Page Setup
  //
  $('.js-authorization-dialog').hide();
  $('.js-logout-container').hide();
  $('.js-userid-container').hide();
  $('.js-authn').hide();
  $('.js-authz').hide();

  $('body').on('click', '.js-logout', util.handleLogout);
  $('body').on('click', '.js-authn-show', util.submitAuthEmail);
  $('body').on('click', '.js-submit-code-btn', util.submitLoginCode);
  $('body').on('click', '.js-login-allow', util.acceptScopesAndLogin);
  $('body').on('click', '.js-login-deny', util.closeLoginDeny);
  $('body').on('click', '.js-edit-email-button', util.editEmail);
  $('body').on('keyup', 'form .js-oauth3-email', util.checkAuthEmail);

  function handleAuthorizationDialog() {
    return getSession(CONFIG.host).then(function (session) {
      return getGrants(session);
    }).catch(function (e) {
      if(e) {
        console.error(
            "Error authing current session. Requiring re-login. Message: "
            , e.message
        );
      }
      // TODO select the providers the client wants to show
      // providers=daplie.com,facebook.com,google.com // etc
      // TODO let the client specify switch_user
      // TODO let the client specify relogin if stale
      if (OAUTH3._browser.isIframe()) {
        location.href = clientParams.redirect_uri +'#'+ OAUTH3.query.stringify({
          state: clientParams.state
        , error: 'access_denied'
        , error_description: encodeURIComponent("You're requesting permission in an iframe, but the user is not yet authenticated")
        , error_uri: encodeURIComponent('https://oauth3.org/docs/errors/#E_IFRAME_DENIED')
        });
      }
      if (clientParams.subject) {
        $('.js-oauth3-email').val(clientParams.subject);
        $('.js-authn-show').prop('disabled', false);
      }
      $('.js-userid-container').show();
    });
  }

  // Session initialization
  return OAUTH3.discover(CONFIG.host, { client_uri: CONFIG.host }).then(function (directives) {
    // TODO cache directives in memory (and storage)
    CONFIG.directives = directives;
    directives.issuer = directives.issuer || (window.location.host + window.location.pathname).replace(/\/$/, '');

    if (/authorization_dialog/.test(window.location.href)) {
      $('.js-authorization-dialog').show();
      handleAuthorizationDialog();
    }
    else if (/logout/.test(window.location.href)) {
      $('.js-logout-container').show();
    }

    if (document.location.hash.slice(1) || document.location.search) {
      console.log('[DEBUG] search:', document.location.search);
      console.log('[DEBUG] hash:', document.location.search);
      $('.mock-main').addClass('in');
    } else {
      console.log('[DEBUG] not an auth window');
      $('.js-playground').addClass('in');
      //window.PLAYGROUND();
    }
  });
});
