jest.mock("../../services/auth");
jest.mock("../../services/getTimerWorker");

import {
  render,
  cleanup,
  screen,
  fireEvent,
  waitFor,
} from "@testing-library/react";
import { UserProvider, useUserContext } from "../Contexts/UserContext";
import auth from "../../services/auth";
import getTimerWorker from "../../services/getTimerWorker";
import { TEST_AUTH_OBJECT, TEST_EXPECTED_GROUPS } from "../../utils/testing";

const INVALID_TOKEN_RESPONSE = {
  error: "Bad Request Error",
  message: "Bad Request Message",
};

const setupRefreshEnv = (refreshResponse, valid) => {
  auth.setRefreshResponse(refreshResponse);
  auth.setVerifyResponse({ valid });
};

const setupSignInEnv = (refreshResponse, valid) => {
  auth.setSignInResponse(refreshResponse);
  auth.setVerifyResponse({ valid });
};

const checkBaseResults = (error, fetching, token, groups) => {
  expect(screen.getByTestId("error").innerHTML).toEqual(error);
  expect(screen.getByTestId("fetching").innerHTML).toEqual(fetching);
  expect(screen.getByTestId("token").innerHTML).toEqual(token);
  expect(screen.getByTestId("groups").innerHTML).toEqual(groups);
};

const checkTokenResults = (timer, token) => {
  expect(timer.start.mock.calls.length).toEqual(1);
  expect(timer.onMessage.mock.calls.length).toEqual(1);
  expect(timer.stop.mock.calls.length).toEqual(0);
  expect(timer.terminate.mock.calls.length).toEqual(0);
  if (!localStorage) {
    expect(localStorage.getItem("token")).toEqual(token);
    localStorage.removeItem("token");
  }
};

describe("UseContext", () => {
  describe("Signin", () => {
    beforeEach(() => {
      const TestComp = () => {
        const { signIn, error, token, groups, fetching } = useUserContext();

        return (
          <>
            <div data-testid="error">{error}</div>
            <div data-testid="fetching">{fetching.toString()}</div>
            <div data-testid="token">{JSON.stringify(token)}</div>
            <div data-testid="groups">{groups}</div>
            <button data-testid="signInNoCode" onClick={() => signIn("")} />
            <button
              data-testid="signInInvalidCode"
              onClick={() => signIn("INVALID_CODE")}
            />
            <button data-testid="signIn" onClick={() => signIn("TEST_CODE")} />
          </>
        );
      };
      render(
        <UserProvider>
          <TestComp />
        </UserProvider>
      );
    });

    afterEach(() => {
      cleanup();
    });

    it("Initial state", () => {
      checkBaseResults("", "false", "null", "");
    });

    it("No auth code", () => {
      fireEvent.click(screen.getByTestId("signInNoCode"));

      checkBaseResults("", "false", "null", "");
    });

    it("Invalid auth code", async () => {
      setupSignInEnv(INVALID_TOKEN_RESPONSE, false);

      fireEvent.click(screen.getByTestId("signInInvalidCode"));

      await waitFor(() =>
        expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
      );

      checkBaseResults(
        "Sign in error. Bad Request Message",
        "false",
        "null",
        ""
      );
    });

    it("Sign in form", async () => {
      const TOKEN_STRING = JSON.stringify(TEST_AUTH_OBJECT);
      const timer = getTimerWorker();

      setupSignInEnv(TEST_AUTH_OBJECT, true);

      fireEvent.click(screen.getByTestId("signIn"));

      await waitFor(() =>
        expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
      );

      checkBaseResults("", "false", TOKEN_STRING, TEST_EXPECTED_GROUPS);
      checkTokenResults(timer, TOKEN_STRING);
    });
  });

  describe("Signout", () => {
    beforeEach(async () => {
      const TestComp = () => {
        const { signIn, signOut, error, token, groups, fetching } =
          useUserContext();
        return (
          <>
            <div data-testid="error">{error}</div>
            <div data-testid="fetching">{fetching.toString()}</div>
            <div data-testid="token">{JSON.stringify(token)}</div>
            <div data-testid="groups">{groups}</div>
            <button data-testid="signIn" onClick={() => signIn("TEST_CODE")} />
            <button data-testid="signOut" onClick={() => signOut()} />
          </>
        );
      };
      render(
        <UserProvider>
          <TestComp />
        </UserProvider>
      );
      auth.setSignInResponse(TEST_AUTH_OBJECT);
      fireEvent.click(screen.getByTestId("signIn"));
      await waitFor(() =>
        expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
      );
    });

    afterEach(() => {
      auth.setSignInResponse({});
      cleanup();
    });

    it("Token cleared", async () => {
      fireEvent.click(screen.getByTestId("signOut"));
      checkBaseResults("", "false", "null", "");
      if (!localStorage) return;
      expect(localStorage.getItem("token")).toBeNull();
    });
  });

  describe("Refresh", () => {
    beforeEach(() => {
      const TestComp = () => {
        const { refresh, error, token, groups, fetching } = useUserContext();

        return (
          <>
            <div data-testid="error">{error}</div>
            <div data-testid="fetching">{fetching.toString()}</div>
            <div data-testid="token">{JSON.stringify(token)}</div>
            <div data-testid="groups">{groups}</div>
            <button data-testid="refreshNoToken" onClick={() => refresh("")} />
            <button
              data-testid="refreshInvalidToken"
              onClick={() => refresh("INVALID_TOKEN")}
            />
            <button
              data-testid="refreshValidToken"
              onClick={() => refresh("TEST_TOKEN")}
            />
          </>
        );
      };
      render(
        <UserProvider>
          <TestComp />
        </UserProvider>
      );
    });

    afterEach(() => {
      cleanup();
    });

    it("Initial state", () => {
      checkBaseResults("", "false", "null", "");
    });

    it("No refresh token", () => {
      fireEvent.click(screen.getByTestId("refreshNoToken"));
      checkBaseResults("Refresh error. Token required", "false", "null", "");
    });

    it("Invalid refresh token", async () => {
      setupRefreshEnv(INVALID_TOKEN_RESPONSE, false);

      fireEvent.click(screen.getByTestId("refreshInvalidToken"));
      await waitFor(() =>
        expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
      );

      checkBaseResults(
        "Refresh error. Bad Request Message",
        "false",
        "null",
        ""
      );
    });

    it("Valid refresh token", async () => {
      const TOKEN_STRING = JSON.stringify(TEST_AUTH_OBJECT);
      const timer = getTimerWorker();

      setupRefreshEnv(TEST_AUTH_OBJECT, true);

      fireEvent.click(screen.getByTestId("refreshValidToken"));
      await waitFor(() =>
        expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
      );

      checkBaseResults("", "false", TOKEN_STRING, TEST_EXPECTED_GROUPS);
      checkTokenResults(timer, TOKEN_STRING);
    });
  });
});
