import waitForExpect from "wait-for-expect";
import {
  useFirstSubmitter,
  useIsFaded,
  useIsFinished,
  usePano,
  useSavePanoSettings,
} from "../components/screens/GamePanel/hooks";

jest.mock("react");
jest.mock("../domain/gameStore");
jest.mock("../domain/apiMethods");
jest.mock("../domain/localStorageMethods");

import { useEffect, useRef, useState } from "react";
import { dispatch, useCurrentRound, useGameId } from "../domain/gameStore";
import { getFirstSubmitter } from "../domain/apiMethods";
import {
  savePanoPositionToLocalStorage,
  savePanoPovToLocalStorage,
} from "../domain/localStorageMethods";

describe("GamePanel hooks", () => {
  describe("useIsFinished", () => {
    beforeEach(() => {
      useEffect.mockImplementation(fn => {
        fn();
      });
    });
    it("returns false if the current round is non-null", () => {
      useCurrentRound.mockReturnValue("1");
      expect(useIsFinished()).toBe(false);
      expect(dispatch.goToSummary).not.toHaveBeenCalled();
    });
    it("returns true and goes to summary if the current round is null", () => {
      useCurrentRound.mockReturnValue(null);
      expect(useIsFinished()).toBe(true);
      expect(dispatch.goToSummary).toHaveBeenCalled();
    });
  });

  describe("usePano", () => {
    beforeEach(() => {
      useEffect.mockImplementation(fn => {
        fn();
      });
    });
    it("creates a new pano", () => {
      useRef.mockImplementation(current => ({ current }));
      const { current } = usePano(
        { current: "pano-div-ref" },
        { lat: 1, lng: 2 },
        { heading: 3, pitch: 4 }
      );
      expect(current).toMatchSnapshot();
    });
    it("does not create a new pano when one exists and just moves old one", () => {
      const setPosition = jest.fn();
      const setPov = jest.fn();
      useRef.mockImplementation(() => ({ current: { setPosition, setPov } }));
      const { current } = usePano(
        { current: "pano-div-ref" },
        { lat: 1, lng: 2 },
        { heading: 3, pitch: 4 }
      );
      expect(current).toMatchSnapshot();
      expect(setPosition).toMatchSnapshot();
      expect(setPov).toMatchSnapshot();
    });
  });

  describe("useSavePanoSettings", () => {
    beforeEach(() => {
      useEffect.mockImplementation(fn => {
        const cleanup = fn();
        if (cleanup) {
          cleanup();
        }
      });
    });
    it("attaches listeners to the given panoRef", () => {
      const listeners = {};
      useSavePanoSettings({
        current: {
          getPosition: () => ({ lat: () => 1, lng: () => 2 }),
          getPov: () => ({ heading: 3, pitch: 4 }),
          addListener: (event, fn) => {
            listeners[event] = fn;
          },
        },
      });
      expect(savePanoPositionToLocalStorage).not.toHaveBeenCalled();
      expect(savePanoPovToLocalStorage).not.toHaveBeenCalled();
      listeners["position_changed"]();
      expect(savePanoPositionToLocalStorage).toHaveBeenCalledWith(1, 2);
      listeners["pov_changed"]();
      expect(savePanoPovToLocalStorage).toHaveBeenCalledWith(3, 4);
    });
    it("does nothing when panoRef.current is null", () => {
      useSavePanoSettings({ current: null });
      expect(savePanoPositionToLocalStorage).not.toHaveBeenCalled();
      expect(savePanoPovToLocalStorage).not.toHaveBeenCalled();
    });
  });

  describe("useIsFaded", () => {
    beforeEach(() => {
      useEffect.mockImplementation(fn => {
        const cleanup = fn();
        if (cleanup) {
          cleanup();
        }
      });
    });
    it("does nothing when first is null", () => {
      useState.mockReturnValue([]);
      useIsFaded(null);
      expect(setTimeout).not.toHaveBeenCalled();
    });
    it("sets faded 2 seconds after first is non-null", () => {
      const setFaded = jest.fn();
      useState.mockReturnValue([false, setFaded]);
      setTimeout.mockImplementation(fn => {
        fn();
      });
      useIsFaded("first-submitter");
      expect(setFaded).toHaveBeenCalledWith(true);
      expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 2000);
      expect(clearTimeout).toHaveBeenCalled();
    });
  });

  describe("useFirstSubmitter", () => {
    beforeEach(() => {
      useGameId.mockReturnValue("test-game-id");
      useCurrentRound.mockReturnValue("1");
      useEffect.mockImplementation(fn => {
        const cleanup = fn();
        if (cleanup) {
          cleanup();
        }
      });
    });
    it("queries to find first submitter if not found yet", async () => {
      const setFirst = jest.fn();
      useState.mockReturnValue([null, setFirst]);
      getFirstSubmitter.mockReturnValue("first");
      dispatch.updateRoundSeconds.mockImplementation(fn => fn(100));
      useFirstSubmitter("rate", 10);
      await waitForExpect(() => {
        expect(setFirst).toHaveBeenCalledWith("first");
      });
      expect(dispatch.updateRoundSeconds).toHaveReturnedWith(10);
      // TODO don't understand why these are losing their call record
      // expect(setInterval).toHaveBeenCalledWith(expect.any(Function), "rate");
      // expect(clearInterval).toHaveBeenCalled();
    });
    it("uses min of cutoff arg and current round seconds", async () => {
      const setFirst = jest.fn();
      useState.mockReturnValue([null, setFirst]);
      getFirstSubmitter.mockReturnValue("first");
      dispatch.updateRoundSeconds.mockImplementation(fn => fn(15));
      useFirstSubmitter("rate", 200);
      await waitForExpect(() => {
        expect(setFirst).toHaveBeenCalledWith("first");
      });
      expect(dispatch.updateRoundSeconds).toHaveReturnedWith(15);
      // TODO don't understand why these are losing their call record
      // expect(setInterval).toHaveBeenCalledWith(expect.anything(), "rate");
      // expect(clearInterval).toHaveBeenCalled();
    });
    it("does nothing after query if it returns null", async () => {
      const setFirst = jest.fn();
      useState.mockReturnValue([null, setFirst]);
      getFirstSubmitter.mockReturnValue(null);
      dispatch.updateRoundSeconds.mockImplementation(fn => fn(100));
      useFirstSubmitter("rate", 10);
      expect(setFirst).not.toHaveBeenCalled();
      expect(dispatch.updateRoundSeconds).not.toHaveBeenCalled();
      // TODO don't understand why these are losing their call record
      // expect(setInterval).toHaveBeenCalledWith(expect.any(Function), "rate");
      // expect(clearInterval).toHaveBeenCalled();
    });
    it("does nothing if there's already a first submitter found", () => {
      const setFirst = jest.fn();
      useState.mockReturnValue(["first", setFirst]);
      const first = useFirstSubmitter("rate", 10);
      expect(first).toBe("first");
      expect(setFirst).not.toHaveBeenCalled();
      expect(setInterval).not.toHaveBeenCalled();
      expect(clearInterval).not.toHaveBeenCalled();
    });
  });
});
