import reducer, {
  ACTIONS,
  back,
  initialState,
  push,
  replace,
} from "../reducer";

jest.mock("uuid");

const screen: ZappRiver = {
  id: "B5678",
  name: "Screen name",
  type: "general_content",
  home: false,
  home_offline: false,
  supports_offline: false,
  // @ts-ignore
  navigations: [{ id: "nav_id" }],
  hooks: { preload_plugins: [] },
};

const screen2: ZappRiver = {
  id: "C9090",
  name: "Screen 2 name",
  type: "general_content",
  home: false,
  home_offline: false,
  supports_offline: false,
  // @ts-ignore
  navigations: [{ id: "nav_id" }],
  hooks: { preload_plugins: [] },
};

const homeScreen: ZappRiver = {
  id: "A1234",
  name: "Home",
  type: "general_content",
  home: true,
  home_offline: true,
  supports_offline: false,
  // @ts-ignore
  navigations: [{ id: "nav_id" }],
  hooks: { preload_plugins: [] },
};

const entry: ZappEntry = {
  id: "C0987",
  title: "Entry",
  type: { value: "feed" },
};

const entry2: ZappEntry = {
  id: "D1234",
  title: "Entry 2",
  type: { value: "video" },
};

describe("ACTIONS", () => {
  it("matches snapshot", () => {
    expect(ACTIONS).toMatchSnapshot();
  });
});

describe("actions", () => {
  describe("push", () => {
    it("returns a push action", () => {
      const route = "/river/A1234";
      const action = push(route, { screen, entry });

      expect(action).toHaveProperty("type", ACTIONS.PUSH);

      expect(action).toHaveProperty(
        "payload",
        expect.objectContaining({
          route,
          screen,
          entry,
        })
      );
    });
  });

  describe("replace", () => {
    it("returns a replace action", () => {
      const route = "/river/A1234";
      const action = replace(route, { screen, entry });

      expect(action).toHaveProperty("type", ACTIONS.REPLACE);

      expect(action).toHaveProperty(
        "payload",
        expect.objectContaining({
          route,
          screen,
          entry,
        })
      );
    });
  });

  describe("back", () => {
    it("returns a back action", () => {
      const backAction = back();

      expect(backAction).toHaveProperty("type", ACTIONS.BACK);

      expect(backAction).toHaveProperty(
        "payload",
        expect.objectContaining({
          key: expect.any(String),
          route: "",
        })
      );
    });
  });
});

describe("reducer", () => {
  const route = "/river/A1234";
  const route2 = "/river/B7890";

  it("has the proper inital state", () => {
    const state = reducer();
    expect(state).toEqual(initialState);
  });

  describe("when a push action is dispatched", () => {
    const action = replace(route, { screen: homeScreen });
    const newState = reducer(initialState, action);
    const action2 = push(route2, { screen, entry });
    const newState2 = reducer(newState, action2);

    it("adds a navigation entry to the stack", () => {
      expect(newState).toHaveProperty("stack");
      // @ts-ignore
      expect(newState.stack.mainStack).toBeArrayOfSize(1);

      expect(newState.stack.mainStack[0]).toHaveProperty(
        "action",
        ACTIONS.REPLACE
      );

      expect(newState.stack.mainStack[0]).toHaveProperty("route", route);

      expect(newState.stack.mainStack[0]).toHaveProperty("state", {
        screen: homeScreen,
        entry: undefined,
      });

      expect(newState2).toHaveProperty("stack");
      // @ts-ignore
      expect(newState2.stack.mainStack).toBeArrayOfSize(2);

      expect(newState2.stack.mainStack[0]).toHaveProperty(
        "action",
        ACTIONS.REPLACE
      );

      expect(newState2.stack.mainStack[0]).toHaveProperty("route", route);

      expect(newState2.stack.mainStack[0]).toHaveProperty("state", {
        screen: homeScreen,
        entry: undefined,
      });

      expect(newState2.stack.mainStack[1]).toHaveProperty(
        "action",
        ACTIONS.PUSH
      );

      expect(newState2.stack.mainStack[1]).toHaveProperty("route", route2);

      expect(newState2.stack.mainStack[1]).toHaveProperty("state", {
        screen,
        entry,
      });
    });

    it("leaves navBar properties in state unchanged", () => {
      expect(newState.options.navBar).toEqual(initialState.options.navBar);
      expect(newState2.options.navBar).toEqual(initialState.options.navBar);
    });
  });

  describe("when a replace action is dispatched", () => {
    const action = replace(route, { screen: homeScreen });
    const newState = reducer(initialState, action);
    const action2 = replace(route2, { screen, entry });
    const newState2 = reducer(newState, action2);

    it("resets the stack and adds an entry when replace is called", () => {
      expect(newState).toHaveProperty("stack");
      // @ts-ignore
      expect(newState.stack.mainStack).toBeArrayOfSize(1);

      expect(newState.stack.mainStack[0]).toHaveProperty(
        "action",
        ACTIONS.REPLACE
      );

      expect(newState.stack.mainStack[0]).toHaveProperty("route", route);

      expect(newState.stack.mainStack[0]).toHaveProperty("state", {
        screen: homeScreen,
        entry: undefined,
      });

      expect(newState2).toHaveProperty("stack");
      // @ts-ignore
      expect(newState2.stack.mainStack).toBeArrayOfSize(1);

      expect(newState2.stack.mainStack[0]).toHaveProperty(
        "action",
        ACTIONS.REPLACE
      );

      expect(newState2.stack.mainStack[0]).toHaveProperty("route", route2);

      expect(newState2.stack.mainStack[0]).toHaveProperty("state", {
        screen,
        entry,
      });
    });

    it("leaves navBar properties in state unchanged", () => {
      expect(newState.options.navBar).toEqual(initialState.options.navBar);
      expect(newState2.options.navBar).toEqual(initialState.options.navBar);
    });
  });

  describe("when a back action is dispatched", () => {
    describe("when there's more than one entry in the stack", () => {
      const action = replace(route, { screen: homeScreen });
      const newState = reducer(initialState, action);
      const action2 = push(route2, { screen, entry });
      const newState2 = reducer(newState, action2);
      const backAction = back();
      const finalState = reducer(newState2, backAction);

      it("removes the last item when going back and there's more than 1 entry", () => {
        expect(finalState).toHaveProperty("stack");

        // @ts-ignore
        expect(finalState.stack.mainStack).toBeArrayOfSize(
          newState2.stack.mainStack.length - 1
        );

        expect(finalState.stack.mainStack[0]).toHaveProperty(
          "action",
          ACTIONS.BACK
        );

        expect(finalState.stack.mainStack[0]).toHaveProperty("route", route);

        expect(finalState.stack.mainStack[0]).toHaveProperty(
          "state",
          expect.objectContaining({ entry: undefined, screen: homeScreen })
        );
      });

      it("leaves navBar properties in state unchanged", () => {
        expect(newState.options.navBar).toEqual(initialState.options.navBar);
        expect(newState2.options.navBar).toEqual(initialState.options.navBar);
        expect(finalState.options.navBar).toEqual(initialState.options.navBar);
      });
    });

    describe("push, back, push, back", () => {
      const route = "/river/A1234";
      const route2 = "/river/A1234/river/B7890";
      const route3 = "/river/A1234/river/B7890/river/C0987";
      const route4 = "/river/A1234/river/B7890/river/D5678";

      const action = replace(route, { screen: homeScreen });
      const state1 = reducer(initialState, action);
      const action2 = push(route2, { screen, entry });
      const state2 = reducer(state1, action2);
      const action3 = push(route3, { screen: screen2, entry: entry2 });
      const state3 = reducer(state2, action3);
      const backAction = back();
      const state4 = reducer(state3, backAction);
      const action4 = push(route4, { screen, entry });
      const state5 = reducer(state4, action4);
      const state6 = reducer(state5, backAction);

      it("does what it's supposed to do", () => {
        expect(true).toBe(true);

        expect({
          state1,
          state2,
          state3,
          state4,
          state5,
          state6,
        }).toMatchSnapshot();
      });
    });

    describe("when there's only one entry in the stack", () => {
      const action = replace(route, { screen: homeScreen });
      const newState = reducer(initialState, action);
      const backAction = back();
      const finalState = reducer(newState, backAction);

      it("returns the same entry when going back and there's one entry", () => {
        expect(finalState).toHaveProperty("stack");

        // @ts-ignore
        expect(finalState.stack.mainStack).toBeArrayOfSize(1);

        expect(finalState.stack.mainStack[0]).toHaveProperty(
          "action",
          ACTIONS.REPLACE
        );

        expect(finalState.stack.mainStack[0]).toHaveProperty("route", route);

        expect(finalState.stack.mainStack[0]).toHaveProperty("state", {
          screen: homeScreen,
          entry: undefined,
        });

        expect(finalState).toEqual(newState);
      });
    });
  });
});
