/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

/**
 * Test FxA logins with Loop.
 */

"use strict";

const {
  gFxAOAuthTokenData,
  gFxAOAuthProfile,
} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});

const BASE_URL = "http://mochi.test:8888/browser/browser/components/loop/test/mochitest/loop_fxa.sjs?";

add_task(function* setup() {
  Services.prefs.setCharPref("loop.server", BASE_URL);
  Services.prefs.setCharPref("services.push.serverURL", "ws://localhost/");
  registerCleanupFunction(function* () {
    info("cleanup time");
    yield promiseDeletedOAuthParams(BASE_URL);
    Services.prefs.clearUserPref("loop.server");
    Services.prefs.clearUserPref("services.push.serverURL");
    resetFxA();
    Services.prefs.clearUserPref(MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.GUEST));
  });
});

add_task(function* checkOAuthParams() {
  let params = {
    client_id: "client_id",
    content_uri: BASE_URL + "/content",
    oauth_uri: BASE_URL + "/oauth",
    profile_uri: BASE_URL + "/profile",
    state: "state",
  };
  yield promiseOAuthParamsSetup(BASE_URL, params);
  let client = yield MozLoopServiceInternal.promiseFxAOAuthClient();
  for (let key of Object.keys(params)) {
    ise(client.parameters[key], params[key], "Check " + key + " was passed to the OAuth client");
  }
  let prefName = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
  let padding = "X".repeat(HAWK_TOKEN_LENGTH - params.client_id.length);
  ise(Services.prefs.getCharPref(prefName), params.client_id + padding, "Check FxA hawk token");
});

add_task(function* basicAuthorization() {
  let result = yield MozLoopServiceInternal.promiseFxAOAuthAuthorization();
  is(result.code, "code1", "Check code");
  is(result.state, "state", "Check state");
});

add_task(function* sameOAuthClientForTwoCalls() {
  resetFxA();
  let client1 = yield MozLoopServiceInternal.promiseFxAOAuthClient();
  let client2 = yield MozLoopServiceInternal.promiseFxAOAuthClient();
  ise(client1, client2, "The same client should be returned");
});

add_task(function* paramsInvalid() {
  resetFxA();
  // Delete the params so an empty object is returned.
  yield promiseDeletedOAuthParams(BASE_URL);
  let result = null;
  let loginPromise = MozLoopService.logInToFxA();
  let caught = false;
  yield loginPromise.catch(() => {
    ok(true, "The login promise should be rejected due to invalid params");
    caught = true;
  });
  ok(caught, "Should have caught the rejection");
  is(result, null, "No token data should be returned");
});

add_task(function* params_no_hawk_session() {
  resetFxA();
  let params = {
    client_id: "client_id",
    content_uri: BASE_URL + "/content",
    oauth_uri: BASE_URL + "/oauth",
    profile_uri: BASE_URL + "/profile",
    state: "state",
    test_error: "params_no_hawk",
  };
  yield promiseOAuthParamsSetup(BASE_URL, params);

  let loginPromise = MozLoopService.logInToFxA();
  let caught = false;
  yield loginPromise.catch(() => {
    ok(true, "The login promise should be rejected due to a lack of a hawk session");
    caught = true;
  });
  ok(caught, "Should have caught the rejection");
  let prefName = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
  ise(Services.prefs.getPrefType(prefName),
      Services.prefs.PREF_INVALID,
      "Check FxA hawk token is not set");
});

add_task(function* params_nonJSON() {
  Services.prefs.setCharPref("loop.server", "https://loop.invalid");
  // Reset after changing the server so a new HawkClient is created
  resetFxA();

  let loginPromise = MozLoopService.logInToFxA();
  let caught = false;
  yield loginPromise.catch(() => {
    ok(true, "The login promise should be rejected due to non-JSON params");
    caught = true;
  });
  ok(caught, "Should have caught the rejection");
  Services.prefs.setCharPref("loop.server", BASE_URL);
});

add_task(function* invalidState() {
  resetFxA();
  let params = {
    client_id: "client_id",
    content_uri: BASE_URL + "/content",
    oauth_uri: BASE_URL + "/oauth",
    profile_uri: BASE_URL + "/profile",
    state: "invalid_state",
  };
  yield promiseOAuthParamsSetup(BASE_URL, params);
  let loginPromise = MozLoopService.logInToFxA();
  yield loginPromise.catch((error) => {
    ok(error, "The login promise should be rejected due to invalid state");
  });
});

add_task(function* basicRegistrationWithoutSession() {
  resetFxA();
  yield promiseDeletedOAuthParams(BASE_URL);

  let caught = false;
  yield MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state").catch((error) => {
    caught = true;
    is(error.code, 401, "Should have returned a 401");
  });
  ok(caught, "Should have caught the error requesting /token without a hawk session");
});

add_task(function* basicRegistration() {
  let params = {
    client_id: "client_id",
    content_uri: BASE_URL + "/content",
    oauth_uri: BASE_URL + "/oauth",
    profile_uri: BASE_URL + "/profile",
    state: "state",
  };
  yield promiseOAuthParamsSetup(BASE_URL, params);
  resetFxA();
  // Create a fake FxA hawk session token
  const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
  Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));

  let tokenData = yield MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
  is(tokenData.access_token, "code1_access_token", "Check access_token");
  is(tokenData.scope, "profile", "Check scope");
  is(tokenData.token_type, "bearer", "Check token_type");
});

add_task(function* registrationWithInvalidState() {
  resetFxA();
  let params = {
    client_id: "client_id",
    content_uri: BASE_URL + "/content",
    oauth_uri: BASE_URL + "/oauth",
    profile_uri: BASE_URL + "/profile",
    state: "invalid_state",
  };
  yield promiseOAuthParamsSetup(BASE_URL, params);

  // Create a fake FxA hawk session token
  const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
  Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));

  let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
  yield tokenPromise.then(body => {
    ok(false, "Promise should have rejected");
  },
  error => {
    is(error.code, 400, "Check error code");
  });
});

add_task(function* registrationWith401() {
  resetFxA();
  let params = {
    client_id: "client_id",
    content_uri: BASE_URL + "/content",
    oauth_uri: BASE_URL + "/oauth",
    profile_uri: BASE_URL + "/profile",
    state: "state",
    test_error: "token_401",
  };
  yield promiseOAuthParamsSetup(BASE_URL, params);

  let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
  yield tokenPromise.then(body => {
    ok(false, "Promise should have rejected");
  },
  error => {
    is(error.code, 401, "Check error code");
  });
});

add_task(function* basicAuthorizationAndRegistration() {
  resetFxA();
  let params = {
    client_id: "client_id",
    content_uri: BASE_URL + "/content",
    oauth_uri: BASE_URL + "/oauth",
    profile_uri: BASE_URL + "/profile",
    state: "state",
  };
  yield promiseOAuthParamsSetup(BASE_URL, params);

  info("registering");
  mockPushHandler.pushUrl = "https://localhost/pushUrl/guest";
  // Notification observed due to the error being cleared upon successful registration.
  let statusChangedPromise = promiseObserverNotified("loop-status-changed");
  yield MozLoopService.register(mockPushHandler);
  yield statusChangedPromise;

  // Normally the same pushUrl would be registered but we change it in the test
  // to be able to check for success on the second registration.
  mockPushHandler.pushUrl = "https://localhost/pushUrl/fxa";

  statusChangedPromise = promiseObserverNotified("loop-status-changed");
  yield loadLoopPanel({loopURL: BASE_URL, stayOnline: true});
  yield statusChangedPromise;
  let loopDoc = document.getElementById("loop").contentDocument;
  let visibleEmail = loopDoc.getElementsByClassName("user-identity")[0];
  is(visibleEmail.textContent, "Guest", "Guest should be displayed on the panel when not logged in");
  is(MozLoopService.userProfile, null, "profile should be null before log-in");
  let loopButton = document.getElementById("loop-call-button");
  is(loopButton.getAttribute("state"), "", "state of loop button should be empty when not logged in");

  let tokenData = yield MozLoopService.logInToFxA();
  yield promiseObserverNotified("loop-status-changed", "login");
  ise(tokenData.access_token, "code1_access_token", "Check access_token");
  ise(tokenData.scope, "profile", "Check scope");
  ise(tokenData.token_type, "bearer", "Check token_type");

  is(MozLoopService.userProfile.email, "test@example.com", "email should exist in the profile data");
  is(MozLoopService.userProfile.uid, "1234abcd", "uid should exist in the profile data");
  is(visibleEmail.textContent, "test@example.com", "the email should be correct on the panel");
  is(loopButton.getAttribute("state"), "active", "state of loop button should be active when logged in");

  let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
  ise(registrationResponse.response.simplePushURL, "https://localhost/pushUrl/fxa",
      "Check registered push URL");

  let loopPanel = document.getElementById("loop-notification-panel");
  loopPanel.hidePopup();
  statusChangedPromise = promiseObserverNotified("loop-status-changed");
  yield loadLoopPanel({loopURL: BASE_URL, stayOnline: true});
  yield statusChangedPromise;
  is(loopButton.getAttribute("state"), "", "state of loop button should return to empty after panel is opened");
  loopPanel.hidePopup();

  info("logout");
  yield MozLoopService.logOutFromFxA();
  checkLoggedOutState();
  registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
  ise(registrationResponse.response, null,
      "Check registration was deleted on the server");
  is(visibleEmail.textContent, "Guest", "Guest should be displayed on the panel again after logout");
  is(MozLoopService.userProfile, null, "userProfile should be null after logout");
});

add_task(function* loginWithParams401() {
  resetFxA();
  let params = {
    client_id: "client_id",
    content_uri: BASE_URL + "/content",
    oauth_uri: BASE_URL + "/oauth",
    profile_uri: BASE_URL + "/profile",
    state: "state",
    test_error: "params_401",
  };
  yield promiseOAuthParamsSetup(BASE_URL, params);
  yield MozLoopService.register(mockPushHandler);

  let loginPromise = MozLoopService.logInToFxA();
  yield loginPromise.then(tokenData => {
    ok(false, "Promise should have rejected");
  },
  error => {
    ise(error.code, 401, "Check error code");
    ise(gFxAOAuthTokenData, null, "Check there is no saved token data");
  });
});

add_task(function* logoutWithIncorrectPushURL() {
  resetFxA();
  let pushURL = "http://www.example.com/";
  mockPushHandler.pushUrl = pushURL;

  // Create a fake FxA hawk session token
  const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
  Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));

  yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURL);
  let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
  ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL");
  mockPushHandler.pushUrl = "http://www.example.com/invalid";
  let caught = false;
  yield MozLoopService.logOutFromFxA().catch((error) => {
    caught = true;
  });
  ok(caught, "Should have caught an error logging out with a mismatched push URL");
  checkLoggedOutState();
  registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
  ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL wasn't deleted");
});

add_task(function* logoutWithNoPushURL() {
  resetFxA();
  let pushURL = "http://www.example.com/";
  mockPushHandler.pushUrl = pushURL;

  // Create a fake FxA hawk session token
  const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
  Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));

  yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURL);
  let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
  ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL");
  mockPushHandler.pushUrl = null;
  let caught = false;
  yield MozLoopService.logOutFromFxA().catch((error) => {
    caught = true;
  });
  ok(caught, "Should have caught an error logging out without a push URL");
  checkLoggedOutState();
  registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
  ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL wasn't deleted");
});

add_task(function* loginWithRegistration401() {
  resetFxA();
  let params = {
    client_id: "client_id",
    content_uri: BASE_URL + "/content",
    oauth_uri: BASE_URL + "/oauth",
    profile_uri: BASE_URL + "/profile",
    state: "state",
    test_error: "token_401",
  };
  yield promiseOAuthParamsSetup(BASE_URL, params);

  let loginPromise = MozLoopService.logInToFxA();
  yield loginPromise.then(tokenData => {
    ok(false, "Promise should have rejected");
  },
  error => {
    ise(error.code, 401, "Check error code");
    ise(gFxAOAuthTokenData, null, "Check there is no saved token data");
  });
});
