import * as redux from "react-redux";
import { mockState } from "../../mockState";
import * as api from "../../request/loginRequest";
import * as authHelper from "../../util/helpers/authScopeHelpers";
import * as reqHelper from "../../request";
import { runSaga } from "redux-saga";
import {
  fetchGoogleUserSuccess,
  fetchUserError,
  fetchUserSuccess,
  forgetPasswordSuccess,
  resetLoginState,
} from "../../store/actions/login/loginActions";
import jwt from "jsonwebtoken";
import {
  authenticateUser,
  fetchGoogleUser,
  fetchUser,
  forgetPassword,
  resetPassword,
  logoutUser,
  refreshToken as ref,
  generateToken,
} from "../../store/saga/loginSaga";
import { setUser } from "../../store/actions/user/userActions";
import { BASE_PAGE } from "../../constants/pages";
import history from "../../store/utils/history";

describe("Login Saga reducer tests", () => {
  let spyOnUseSelector;
  let spyOnUseDispatch;
  let mockDispatch;

  beforeEach(() => {
    // Mock useSelector hook
    spyOnUseSelector = jest.spyOn(redux, "useSelector");
    spyOnUseSelector.mockReturnValueOnce(mockState.user.user);

    // Mock useDispatch hook
    spyOnUseDispatch = jest.spyOn(redux, "useDispatch");

    // Mock dispatch function returned from useDispatch
    mockDispatch = jest.fn();
    spyOnUseDispatch.mockReturnValue(mockDispatch);
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  it("should run fetchUser saga function with actions", async () => {
    const dispatchedActions = [];

    const res = { data: { token: "token", refreshToken: "token" } };
    api.attemptLogin = jest.fn(() => Promise.resolve(res));

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    const mockfn = jest.fn();

    await runSaga(fakeStore, fetchUser, {
      payload: {
        password: "pass",
        username: "username",
        handleApiResponseSuccess: mockfn,
      },
    }).done;

    expect(api.attemptLogin.mock.calls.length).toBe(1);
    expect(mockfn).toHaveBeenCalled();
    expect(dispatchedActions).toContainEqual(fetchUserSuccess(res.data));
    expect(dispatchedActions).toContainEqual(setUser(res.data));
  });

  it("should handle fetchUser saga error with actions", async () => {
    const dispatchedActions = [];

    const res = { response: { data: { message: "Error" } } };
    api.attemptLogin = jest.fn(() => Promise.reject(res));

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    const mockfn = jest.fn();

    await runSaga(fakeStore, fetchUser, {
      payload: {
        password: "pass",
        username: "username",
        handleApiResponseFailed: mockfn,
      },
    }).done;

    expect(api.attemptLogin.mock.calls.length).toBe(1);
    expect(mockfn).toHaveBeenCalled();
    expect(dispatchedActions).toContainEqual(fetchUserError(res.data));
  });

  it("should run forgetPassword saga function with actions", async () => {
    const dispatchedActions = [];

    const res = { data: {} };
    api.forgetPasswordEmailSend = jest.fn(() => Promise.resolve(res));

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    const mockfn = jest.fn();

    await runSaga(fakeStore, forgetPassword, {
      payload: {
        email: "email",
        handleApiResponseSuccess: mockfn,
      },
    }).done;

    expect(api.forgetPasswordEmailSend.mock.calls.length).toBe(1);
    expect(mockfn).toHaveBeenCalled();
    expect(dispatchedActions).toContainEqual(forgetPasswordSuccess(res.data));
  });

  it("should handle forgetPassword saga error with actions", async () => {
    const dispatchedActions = [];

    const res = { response: { data: { message: "Error" } } };
    api.forgetPasswordEmailSend = jest.fn(() => Promise.reject(res));

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    const mockfn = jest.fn();

    await runSaga(fakeStore, forgetPassword, {
      payload: {
        password: "pass",
        username: "username",
        handleApiResponseFailed: mockfn,
      },
    }).done;

    expect(api.forgetPasswordEmailSend.mock.calls.length).toBe(1);
    expect(mockfn).toHaveBeenCalled();
    expect(dispatchedActions).toContainEqual(fetchUserError(res.data));
  });

  it("should run reset password saga function with actions", async () => {
    const dispatchedActions = [];

    const res = { data: {} };
    api.sendResetPassword = jest.fn(() => Promise.resolve(res));

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    const mockfn = jest.fn();

    await runSaga(fakeStore, resetPassword, {
      payload: {
        code: "code",
        email: "mail",
        password: "password",
        handleApiResponseSuccess: mockfn,
      },
    }).done;

    expect(api.sendResetPassword.mock.calls.length).toBe(1);
    expect(mockfn).toHaveBeenCalled();
    expect(dispatchedActions).toContainEqual(forgetPasswordSuccess(res.data));
  });

  it("should handle forgetPassword saga error with actions", async () => {
    const dispatchedActions = [];

    const res = { response: { data: { message: "Error" } } };
    api.sendResetPassword = jest.fn(() => Promise.reject(res));

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    const mockfn = jest.fn();

    await runSaga(fakeStore, resetPassword, {
      payload: {
        code: "code",
        email: "mail",
        password: "password",
        handleApiResponseFailed: mockfn,
      },
    }).done;

    expect(api.sendResetPassword.mock.calls.length).toBe(1);
    expect(mockfn).toHaveBeenCalled();
    expect(dispatchedActions).toContainEqual(fetchUserError(res.data));
  });

  it("should run fetch google user saga function with actions", async () => {
    const dispatchedActions = [];

    const res = { data: { token: "token" } };
    api.attemptGoogleLogin = jest.fn(() => Promise.resolve(res));

    authHelper.authScopeSetHelper = jest.fn();

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    const mockfn = jest.fn();

    await runSaga(fakeStore, fetchGoogleUser, {
      payload: {
        user: "mail",
        token: "token",
        handleApiResponseSuccess: mockfn,
      },
    }).done;

    expect(api.attemptGoogleLogin.mock.calls.length).toBe(1);
    expect(mockfn).toHaveBeenCalled();
    expect(authHelper.authScopeSetHelper).toHaveBeenCalled();
    expect(dispatchedActions).toContainEqual(fetchGoogleUserSuccess(res.data));
    expect(dispatchedActions).toContainEqual({
      payload: { token: "token" },
      type: "[SUCCESS]LOGIN_GOOGLE_USER",
    });
  });

  it("should handle fetch google error with actions", async () => {
    const dispatchedActions = [];

    const res = { response: { data: { message: "Error" } } };
    api.attemptGoogleLogin = jest.fn(() => Promise.reject(res));

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    const mockfn = jest.fn();

    await runSaga(fakeStore, fetchGoogleUser, {
      payload: {
        user: "mail",
        token: "token",
        handleApiResponseFailed: mockfn,
      },
    }).done;

    expect(api.attemptGoogleLogin.mock.calls.length).toBe(1);
    expect(mockfn).toHaveBeenCalled();
    expect(dispatchedActions).toContainEqual(fetchUserError(res.data));
  });

  it("should run authenticate user saga function with actions if token does not exist", async () => {
    const dispatchedActions = [];

    const res = { data: { token: null } };
    api.attemptGoogleLogin = jest.fn(() => Promise.resolve(res));

    authHelper.authScopeStringGetHelper = jest.fn(() => "");

    const mockHistoryPush = jest.fn();
    history.push = mockHistoryPush;

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, authenticateUser).done;

    expect(mockHistoryPush).toHaveBeenCalledWith(BASE_PAGE);
    expect(dispatchedActions).not.toContainEqual(
      fetchUserSuccess(res.data.token)
    );
  });

  it("should run authenticate user saga function with actions if token exists", async () => {
    const dispatchedActions = [];

    const res = { data: { token: "token" } };
    api.attemptGoogleLogin = jest.fn(() => Promise.resolve(res));

    authHelper.authScopeStringGetHelper = jest.fn(() => "token");

    const mockHistoryPush = jest.fn();
    history.push = mockHistoryPush;

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, authenticateUser).done;

    expect(mockHistoryPush).not.toHaveBeenCalled();
    expect(dispatchedActions).toContainEqual({
      payload: { JwtToken: "token" },
      type: "[SUCCESS]LOGIN_USER",
    });
  });

  it("should run authenticate user saga function and handle errors", async () => {
    const dispatchedActions = [];
    const res = { response: { data: { message: "message" } } };
    authHelper.authScopeStringGetHelper = jest.fn(() => Promise.reject(res));

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, authenticateUser).done;

    expect(dispatchedActions).toContainEqual({
      // undefined because we have not mocked rejectCodeHelper
      payload: undefined,
      type: "[ERROR]LOGIN_USER",
    });
  });

  it("should run logout user saga function with actions", async () => {
    const dispatchedActions = [];

    const res = { data: { token: "token" } };
    // simply mock it to ensure we can test if method has been called
    api.logoutUserRequest = jest.fn(() => Promise.resolve(res));

    authHelper.authScopeStringGetHelper = jest.fn(() => "token");
    authHelper.authScopeClearHelper = jest.fn();
    reqHelper.removeHeaderToken = jest.fn();

    const mockUser = { id: 1 };

    jwt.decode = jest.fn(() => mockUser);

    const mockHistoryReplace = jest.fn();
    history.replace = mockHistoryReplace;

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, logoutUser).done;

    expect(mockHistoryReplace).toHaveBeenCalled();
    expect(authHelper.authScopeClearHelper).toHaveBeenCalled();
    expect(reqHelper.removeHeaderToken).toHaveBeenCalled();
    expect(api.logoutUserRequest).toHaveBeenCalledWith(mockUser.id);
    expect(dispatchedActions).toContainEqual(resetLoginState());
  });

  it("should run refreshToken saga function with actions", async () => {
    const dispatchedActions = [];

    const res = { data: { user: "user", token: "token" } };
    api.refreshTokenRequest = jest.fn(() => Promise.resolve(res));

    authHelper.authScopeStringGetHelper = jest.fn();
    authHelper.authScopeStringGetHelper
      .mockReturnValueOnce("Token")
      .mockReturnValueOnce("RefreshToken");

    authHelper.authScopeSetHelper = jest.fn();
    reqHelper.addHeaderToken = jest.fn();

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    await runSaga(fakeStore, ref).done;

    expect(authHelper.authScopeStringGetHelper).toHaveBeenCalled();
    expect(api.refreshTokenRequest).toHaveBeenCalledWith({
      refreshToken: "RefreshToken",
      token: "Token",
    });
  });

  it("should run generateToken saga function with actions", async () => {
    const dispatchedActions = [];

    const res = { data: { JwtToken: "jwt", JwtRefreshToken: "refresh" } };
    api.generateTokenRequest = jest.fn(() => Promise.resolve(res));

    authHelper.authScopeSetHelper = jest.fn();
    authHelper.authScopeStringGetHelper = jest.fn();
    authHelper.authScopeStringGetHelper
      .mockReturnValueOnce("Token")
      .mockReturnValueOnce("RefreshToken");
    reqHelper.addHeaderToken = jest.fn();

    const fakeStore = {
      getState: () => ({
        email: "",
        errorMessage: "",
      }),
      dispatch: (action) => dispatchedActions.push(action),
    };

    var mockSession = jest.fn();
    Storage.prototype.setItem = mockSession;

    const mockUser = { id: 1 };
    jwt.decode = jest.fn(() => mockUser);

    const mockSuccess = jest.fn()

    await runSaga(fakeStore, generateToken, {
      payload: {
        data: { data: "data" },
        impersonate: { data: "data" },
        registration: { data: "data" },
        onSuccess: mockSuccess
      },
    }).done;

    expect(authHelper.authScopeSetHelper).toHaveBeenCalled();
    expect(mockSession).toHaveBeenCalled();
    expect(mockSuccess).toHaveBeenCalled();
    expect(dispatchedActions).toContainEqual(setUser(mockUser));
  });
});
