import { renderHook } from "@testing-library/react-hooks";
import * as R from "ramda";
import * as zappPipesModule from "@applicaster/zapp-react-native-redux/ZappPipes";
import * as reactReduxModules from "react-redux";
import * as React from "react";
import * as useRouteHook from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useRoute";
import * as useNavigationHooks from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation";
import { useFeedLoader } from "../useFeedLoader";
import { WrappedWithProviders } from "../../../testUtils";
import { ScreenStateResolver } from "../../../appUtils/contextKeysManager/contextResolver";

jest.useFakeTimers({ legacyFakeTimers: true });

const mockLogger = {
  warning: jest.fn(),
  log: jest.fn(),
  info: jest.fn(),
};

const mockNavigator: any = {
  currentRoute: "/river/A1234",
  getStack: jest.fn(() => []),
};

const mockRouteData: any = {
  screenData: {},
  pathname: "/river/A1234",
};

jest.doMock("../../logger", () => ({
  reactHooksLogger: {
    addSubsystem: jest.fn(() => mockLogger),
  },
}));

jest
  .spyOn(useNavigationHooks, "useNavigation")
  .mockImplementation(() => mockNavigator);

jest.spyOn(useRouteHook, "useRoute").mockImplementation(() => mockRouteData);

const mockZappPipesData = {
  data: {
    id: 1,
    entry: [{ title: "test", type: { value: "feed" } }],
    type: { value: "feed" },
    title: "Winning Programs",
  },
  loading: "false",
  url: "test://testfakeurl",
};

describe("useFeedLoader", () => {
  describe("with cached feed url", () => {
    const store = {
      plugins: [],
      zappPipes: { "test://testfakeurl": mockZappPipesData },
    };

    const wrapper: React.FC<any> = ({ children, ...props }) => (
      <WrappedWithProviders store={props.store || store}>
        {children}
      </WrappedWithProviders>
    );

    it("returns cached feed", () => {
      const { result } = renderHook(
        () => useFeedLoader({ feedUrl: "test://testfakeurl" }),
        {
          wrapper,
        }
      );

      expect(R.omit(["reloadData", "loadNext"], result.current)).toEqual(
        mockZappPipesData
      );
    });

    it("Tries to load newFeed it feedUrl changes", () => {
      const useDispatchSpy = jest
        .spyOn(reactReduxModules, "useDispatch")
        .mockImplementation(() => jest.fn());

      const loadPipesDataSpy = jest
        .spyOn(zappPipesModule, "loadPipesData")
        .mockImplementation(jest.fn());

      const { rerender } = renderHook(
        () => useFeedLoader({ feedUrl: "test://testfakeurl" }),
        {
          wrapper,
        }
      );

      expect(loadPipesDataSpy).not.toBeCalled();

      rerender({ feedUrl: "test" });

      expect(loadPipesDataSpy).not.toBeCalledWith({
        feedUrl: "test",
        pipesOptions: {},
      });

      loadPipesDataSpy.mockRestore();
      useDispatchSpy.mockRestore();
    });
  });

  describe("without cached feeds", () => {
    const feedUrl = "test://testfakeurl2";

    const wrapper: React.FC<any> = ({ children, ...props }) => (
      <WrappedWithProviders store={props.store}>
        {children}
      </WrappedWithProviders>
    );

    it("It loads data for new url and returns it", () => {
      const useDispatchSpy = jest
        .spyOn(reactReduxModules, "useDispatch")
        .mockImplementation(() => jest.fn());

      const loadPipesDataSpy = jest
        .spyOn(zappPipesModule, "loadPipesData")
        .mockImplementation(jest.fn());

      const initialStore = {
        plugins: [],
        zappPipes: { "test://testfakeurl": "foobar" },
      };

      const { result, rerender } = renderHook(
        () => useFeedLoader({ feedUrl: "test://testfakeurl2" }),
        { wrapper, initialProps: { store: initialStore } }
      );

      expect(result.current.data).toBeNull();

      expect(loadPipesDataSpy).toHaveBeenCalledWith(feedUrl, {
        clearCache: true,
        riverId: undefined,
        callback: expect.any(Function),
        resolvers: {
          screen: expect.any(ScreenStateResolver),
        },
      });

      const store2 = {
        plugins: [],
        zappPipes: { "test://testfakeurl2": mockZappPipesData },
      };

      rerender({ store: store2 });

      expect(R.omit(["reloadData", "loadNext"], result.current)).toEqual(
        mockZappPipesData
      );

      loadPipesDataSpy.mockRestore();
      useDispatchSpy.mockRestore();
    });

    it("It loads data for new url and calls onFeedLoaded if provided", () => {
      const loadPipesDataSpy = jest
        .spyOn(zappPipesModule, "loadPipesData")
        .mockImplementation(jest.fn());

      const useDispatchSpy = jest
        .spyOn(reactReduxModules, "useDispatch")
        .mockImplementation(() => jest.fn());

      const initialStore = {
        plugins: [],
        zappPipes: { "test://testfakeurl": "foobar" },
      };

      const { result, rerender } = renderHook(
        () => useFeedLoader({ feedUrl: "test://testfakeurl2" }),
        { wrapper, initialProps: { store: initialStore } }
      );

      expect(result.current.data).toBeNull();

      expect(loadPipesDataSpy.mock.calls[0][0]).toBe(feedUrl);

      expect(loadPipesDataSpy.mock.calls[0][1]).toMatchObject({
        clearCache: true,
        riverId: undefined,
        resolvers: {
          screen: {
            screenStateStore: expect.any(Function),
          },
        },
      });

      const store2 = {
        plugins: [],
        zappPipes: { "test://testfakeurl2": mockZappPipesData },
      };

      rerender({ store: store2 });

      loadPipesDataSpy.mockRestore();
      useDispatchSpy.mockRestore();
    });
  });

  describe("on callbacks called", () => {
    const feedUrl = "test://testfakeurl";
    const feedUrlWithNext = "test://withnexttestfakeurl";

    const wrapper: React.FC<any> = ({ children, ...props }) => (
      <WrappedWithProviders store={props.store || {}}>
        {children}
      </WrappedWithProviders>
    );

    describe("reloadData", () => {
      it("Tries to reload data in the redux store", () => {
        const loadPipesDataSpy = jest
          .spyOn(zappPipesModule, "loadPipesData")
          .mockImplementation(jest.fn());

        const useDispatchSpy = jest
          .spyOn(reactReduxModules, "useDispatch")
          .mockImplementation(() => jest.fn());

        const initialStore = {
          plugins: [],
          zappPipes: { [feedUrl]: "foobar" },
        };

        const { result } = renderHook(() => useFeedLoader({ feedUrl }), {
          wrapper,
          initialProps: { store: initialStore },
        });

        const { reloadData } = result.current;

        reloadData?.();

        expect(loadPipesDataSpy).toHaveBeenCalled();

        expect(
          loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][0]
        ).toBe(feedUrl);

        expect(
          loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][1]
        ).toMatchObject({
          clearCache: true,
          silentRefresh: true,
          resolvers: {
            screen: {
              screenStateStore: expect.any(Function),
            },
          },
        });

        loadPipesDataSpy.mockRestore();
        useDispatchSpy.mockRestore();
      });
    });

    describe("loadNext", () => {
      it("Tries to load next data for the feed", () => {
        const nextUrl = "test://nextUrl";

        const loadPipesDataSpy = jest
          .spyOn(zappPipesModule, "loadPipesData")
          .mockImplementation(jest.fn());

        const useDispatchSpy = jest
          .spyOn(reactReduxModules, "useDispatch")
          .mockImplementation(() => jest.fn());

        const initialStore = {
          plugins: [],
          zappPipes: { [feedUrlWithNext]: { data: { next: nextUrl } } },
        };

        const { result } = renderHook(
          () => useFeedLoader({ feedUrl: feedUrlWithNext }),
          {
            wrapper,
            initialProps: { store: initialStore },
          }
        );

        const { loadNext } = result.current;

        loadNext?.();

        expect(loadPipesDataSpy).toHaveBeenCalled();

        expect(
          loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][0]
        ).toBe(nextUrl);

        expect(
          loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][1]
        ).toMatchObject({
          parentFeed: feedUrlWithNext,
          silentRefresh: true,
          resolvers: {
            screen: {
              screenStateStore: expect.any(Function),
            },
          },
        });

        loadPipesDataSpy.mockRestore();
        useDispatchSpy.mockRestore();
      });
    });
  });
});
