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

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/timer";

const TEST_TOKEN = { idToken: { 
  jwtToken: "TEST",
  payload: {
    exp: (new Date().getTime() / 1000)
  }
}};

describe("UseContext", () => {
  
  describe("Signup", () => {
    beforeEach(() => {
      const TestComp = () => {
        const { signUp, error, fetching } = useUserContext();
        return (
          <>
            <div data-testid="error">{error}</div>
            <div data-testid="fetching">{fetching.toString()}</div>
            <button data-testid="signUpNoEmail" onClick={() => signUp("")}/>
            <button data-testid="signUpNoPassword" onClick={() => signUp("test@test.com", "")}/>
            <button data-testid="signUpBadConfirm" onClick={() => signUp("test@test.com", "password", "")}/>
            <button data-testid="signUp" onClick={() => signUp("test@test.com", "password", "password")}/>        
          </>
        );
      };
      render(<UserProvider><TestComp /></UserProvider>);  
    });

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

    it("Initial state", () => {
      expect(screen.getByTestId("error").innerHTML).toEqual("");
      expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
    });

    it("Error with no email address", () => {
      fireEvent.click(screen.getByTestId("signUpNoEmail"));
      expect(screen.getByTestId("error").innerHTML).toEqual("Email is required");
      expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
    });

    it("Error with no password", () => {
      fireEvent.click(screen.getByTestId("signUpNoPassword"));
      expect(screen.getByTestId("error").innerHTML).toEqual("Password is required");
      expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
    });

    it("Error with non-matching password", () => {
      fireEvent.click(screen.getByTestId("signUpBadConfirm"));
      expect(screen.getByTestId("error").innerHTML).toEqual("Passwords do not match");
      expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
    });

    it("No error sign up", async () => {
      fireEvent.click(screen.getByTestId("signUp"));
      await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
      expect(screen.getByTestId("error").innerHTML).toEqual("");
    });

    it("Handle server error", async () => {
      auth.setSignInResponse({ message: "SIGN-IN-ERROR" })
      auth.setSignUpResponse({ message: "SIGN-UP-ERROR", error: "ERR" });
      fireEvent.click(screen.getByTestId("signUp"));
      await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
      expect(screen.getByTestId("error").innerHTML).toEqual("SIGN-UP-ERROR");
      auth.setSignUpResponse({});
    });
  });

  describe("Signin", () => {

    beforeEach(() => {
      const TestComp = () => {
        const { signIn, error, token, fetching } = useUserContext();
        return (
          <>
            <div data-testid="error">{error}</div>
            <div data-testid="fetching">{fetching.toString()}</div>
            <div data-testid="token">{JSON.stringify(token)}</div>
            <button data-testid="signInNoEmail" onClick={() => signIn("")}/>
            <button data-testid="signInNoPassword" onClick={() => signIn("test@test.com", "")}/>
            <button data-testid="signIn" onClick={() => signIn("test@test.com", "password", "password")}/>          
          </>
        );
      };
      render(<UserProvider><TestComp /></UserProvider>);  
    });

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

    it("Initial state", () => {
      expect(screen.getByTestId("error").innerHTML).toEqual("");
      expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
      expect(screen.getByTestId("token").innerHTML).toEqual("null");
    });

    it("Error with no email address", () => {
      fireEvent.click(screen.getByTestId("signInNoEmail"));
      expect(screen.getByTestId("error").innerHTML).toEqual("Email is required");
      expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
      expect(screen.getByTestId("token").innerHTML).toEqual("null");
    });

    it("Error with no password", () => {
      fireEvent.click(screen.getByTestId("signInNoPassword"));
      expect(screen.getByTestId("error").innerHTML).toEqual("Password is required");
      expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
      expect(screen.getByTestId("token").innerHTML).toEqual("null");
    });

    it("No error sign in", async () => {
      const TOKEN_STRING = JSON.stringify(TEST_TOKEN);
      const timer = getTimerWorker();

      auth.setSignInResponse(TEST_TOKEN);
      auth.setVerifyResponse({ authenticated: true })
      fireEvent.click(screen.getByTestId("signIn"));
      await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
      expect(screen.getByTestId("error").innerHTML).toEqual("");
      expect(screen.getByTestId("token").innerHTML).toEqual(TOKEN_STRING);
      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_STRING);
        localStorage.removeItem("token");  
      }
  });

    it("Handle server error", async () => {
      auth.setSignInResponse({ message: "SERVER-ERROR", error: "ERR" });
      fireEvent.click(screen.getByTestId("signIn"));
      await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
      expect(screen.getByTestId("error").innerHTML).toEqual("SERVER-ERROR");
      auth.setSignUpResponse({});
    });
  });

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

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

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