import React from "react";
import { View } from "react-native";
import { act, create } from "react-test-renderer";
import { createStore } from "redux";
import { Provider } from "react-redux";

import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
import { getByTestId } from "@applicaster/zapp-react-native-utils/testUtils/getByTestId";
// @TODO @szhigunov to complete the migration to @testing-library/react-native
// import { act, waitFor } from "@testing-library/react-native";

const rivers = {
  A1234: {
    id: "A1234",
    navigations: [],
    ui_components: [],
  },
  B4567: {
    home: true,
    id: "B4567",
    navigations: [],
    ui_components: [],
  },
  C0987: {
    id: "C0987",
    navigations: [],
    ui_components: [],
    hooks: {
      postload_plugins: [],
      preload_plugins: [
        {
          screen_id: "hook_screen",
          identifier: "test-hook",
          type: "general",
          weight: 7,
        },
        {
          screen_id: "hook_screen2",
          identifier: "test-hook2",
          type: "general",
          weight: 7,
        },
      ],
    },
  },
  D6543: {
    id: "D6543",
    navigations: [],
    ui_components: [],
    hooks: {
      preload_plugins: [
        {
          screen_id: "failing_hook_screen",
          identifier: "failing-hook",
          type: "general",
          weight: 0,
        },
      ],
    },
  },
  hook_screen: {
    id: "hook_screen",
    type: "test-hook",
    general: {
      allow_screen_plugin_presentation: true,
    },
  },
  hook_screen2: {
    id: "hook_screen2",
    type: "test-hook2",
    general: {
      allow_screen_plugin_presentation: true,
    },
  },
  failing_hook_screen: {
    id: "failing_hook_screen",
    type: "failing-hook",
    general: {
      allow_screen_plugin_presentation: true,
    },
  },
};

jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
  usePickFromState: jest.fn(() => ({
    appData: { layoutVersion: "v2" },
    rivers,
    plugins: [],
    appState: { appLaunched: true },
  })),
  useContentTypes: jest.fn(() => ({})),
}));

type Props = {
  callback: (arg: unknown) => void;
  payload: {
    id: string;
  };
  otherProps: any;
};

jest.mock("@applicaster/zapp-react-native-utils/reactHooks/connection", () => ({
  useConnectionInfo: jest.fn(() => ({
    details: {},
    isConnected: true,
    isInternetReachable: true,
  })),
}));

const { NavigationProvider } = require("../NavigationProvider");

// eslint-disable-next-line react/prop-types
const HookComponent = ({ callback, payload }: Props) => {
  if (payload?.id === "D6543") {
    callback({ success: false, payload });

    return null;
  }

  callback({ success: true, payload });

  // @ts-ignore
  return <View />;
};

const hookPlugin = {
  module: {
    Component: HookComponent,
    presentFullScreen: true,
  },
  type: "general",
  identifier: "test-hook",
  name: "Test Hook",
};

const hookPlugin2 = {
  module: {
    Component: HookComponent,
    presentFullScreen: true,
  },
  type: "general",
  identifier: "test-hook2",
  name: "Test Hook 2",
};

const failingHook = {
  module: {
    Component: HookComponent,
    presentFullScreen: true,
    isFlowBlocker: () => true,
  },
  type: "general",
  identifier: "failing-hook",
  name: "failing hook",
};

// const store = {
//   rivers,
//   plugins: [hookPlugin, hookPlugin2, failingHook],
// };

const store = createStore(() => ({
  appData: { layoutVersion: "v1" },
  contentTypes: null,
  rivers,
  plugins: [hookPlugin, hookPlugin2, failingHook],
  appState: { appLaunched: true },
}));

const ContextConsumer = () => {
  const { state, ...navigator } = useNavigation();

  let renderHook;

  if (navigator.currentRoute.includes("/hooks/")) {
    const { hookPlugin, payload, callback } = navigator.screenData;
    const Component = hookPlugin.module.Component;

    renderHook = <Component payload={payload} callback={callback} />;
  }

  renderHook = null;

  const WrapperView: any = "View";

  return (
    <WrapperView testID="WrapperView" navigator={navigator} state={state}>
      {renderHook}
    </WrapperView>
  );
};

const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});

// const renderContextConsumer = (_store = store) => {
//   return renderWithProviders(<ContextConsumer />, _store);
// };

const renderContextConsumer = (_store = store) => {
  return create(
    <Provider store={store}>
      <NavigationProvider>
        <ContextConsumer />
      </NavigationProvider>
    </Provider>
  );
};

afterAll(() => {
  consoleSpy.mockReset();
});

describe("<NavigationProvider />", () => {
  let wrapper;

  it("navigator contains expected properties", () => {
    act(() => {
      wrapper = renderContextConsumer();
    });

    const view = getByTestId(wrapper, "WrapperView");

    const { navigator } = view.props;
    expect(navigator.activeRiver).toEqual(rivers.B4567);
    expect(navigator.currentRoute).toEqual("/river/B4567");
    expect(navigator.previousAction).toEqual("REPLACE");

    expect(navigator.screenData).toEqual({
      ...rivers.B4567,
      targetScreen: rivers.B4567,
    });

    expect(navigator.routeData()).toEqual({
      ...rivers.B4567,
      targetScreen: rivers.B4567,
    });
  });

  describe("pushing a new route", () => {
    act(() => {
      wrapper = renderContextConsumer();
    });

    it("sets the data to that route", async () => {
      const view = getByTestId(wrapper, "WrapperView");

      act(() => view.props.navigator.push(rivers.A1234));

      expect(view.props.navigator).toHaveProperty("activeRiver", rivers.A1234);

      expect(view.props.navigator).toHaveProperty(
        "currentRoute",
        "/river/B4567/river/A1234"
      );

      expect(view.props.navigator.screenData).toEqual({
        ...rivers.A1234,
        targetScreen: rivers.A1234,
      });

      expect(view.props.state.stack.mainStack).toHaveLength(2);
    });
  });

  describe("replacing a current route", () => {
    act(() => {
      wrapper = renderContextConsumer();
    });

    const view = getByTestId(wrapper, "WrapperView");

    it("sets the data to that route", () => {
      act(() => view.props.navigator.replace(rivers.A1234));
      expect(view.props.navigator).toHaveProperty("activeRiver", rivers.A1234);

      expect(view.props.navigator).toHaveProperty(
        "currentRoute",
        "/river/A1234"
      );

      expect(view.props.navigator.screenData).toEqual({
        ...rivers.A1234,
        targetScreen: rivers.A1234,
      });

      expect(view.props.state.stack.mainStack).toHaveLength(1);
    });
  });

  describe("going back", () => {
    describe("when there is no previous route", () => {
      act(() => {
        wrapper = renderContextConsumer();
      });

      const view = getByTestId(wrapper, "WrapperView");

      it("returns false when calling canGoBack", () => {
        let result;

        act(() => {
          result = view.props.navigator.canGoBack();
        });

        expect(result).toBe(false);
      });

      it("stays on the current route", () => {
        act(() => view.props.navigator.goBack());

        expect(view.props.navigator).toHaveProperty(
          "activeRiver",
          rivers.B4567
        );

        expect(view.props.navigator).toHaveProperty(
          "currentRoute",
          "/river/B4567"
        );

        expect(view.props.navigator.screenData).toEqual({
          ...rivers.B4567,
          targetScreen: rivers.B4567,
        });

        expect(view.props.state.stack.mainStack).toHaveLength(1);
      });
    });

    describe("when there is one route and we're going in a hook", () => {
      act(() => {
        wrapper = renderContextConsumer();
      });

      const view = getByTestId(wrapper, "WrapperView");
      act(() => view.props.navigator.replace(rivers.A1234));

      it("returns true when calling goBack()", () => {
        act(() => view.props.navigator.push(rivers.C0987));
        expect(view.props.state.stack.mainStack.length).toBeGreaterThan(1);
        expect(view.props.navigator.canGoBack()).toEqual(true);
      });

      // this test is skipped as it doesn't work on this version of react
      // where we can't properly test async actions within components
      it.skip("returns to the previous route when the hook is cancelled", () => {
        act(() => view.props.navigator.replace(rivers.A1234));
        act(() => view.props.navigator.push(rivers.D6543));

        expect(view.props.navigator).toHaveProperty(
          "activeRiver",
          rivers.A1234
        );

        expect(view.props.navigator).toHaveProperty(
          "currentRoute",
          "/river/A1234"
        );

        expect(view.props.navigator.screenData).toEqual(rivers.A1234);
        expect(view.props.state.stack.mainStack).toHaveLength(1);
      });
    });

    describe("when there is a previous route", () => {
      act(() => {
        wrapper = renderContextConsumer();
      });

      const view = getByTestId(wrapper, "WrapperView");

      act(() => view.props.navigator.push(rivers.A1234));

      act(() => view.props.navigator.push({ ...rivers.B4567, id: "B4567-1" }));

      it("returns true when calling canGoBack", () => {
        let result;

        act(() => {
          result = view.props.navigator.canGoBack();
        });

        expect(result).toBeTruthy();
      });

      it("returns true when calling canGoBack(false, pathname)", () => {
        let result;

        act(() => {
          result = view.props.navigator.canGoBack(false, "B4567-1");
        });

        expect(result).toBeTruthy();

        act(() => {
          result = view.props.navigator.canGoBack(false, "A1234");
        });

        expect(result).toBeTruthy();
      });

      it("returns to the previous route", () => {
        expect(view.props.state.stack.mainStack).toHaveLength(3);

        act(() => view.props.navigator.goBack());

        expect(view.props.navigator).toHaveProperty(
          "activeRiver",
          rivers.A1234
        );

        expect(view.props.navigator).toHaveProperty(
          "currentRoute",
          "/river/B4567/river/A1234"
        );

        expect(view.props.navigator.screenData).toEqual({
          ...rivers.A1234,
          targetScreen: rivers.A1234,
        });

        expect(view.props.state.stack.mainStack).toHaveLength(2);
      });
    });

    describe("when there is a previous route and there are hooks", () => {
      act(() => {
        wrapper = renderContextConsumer();
      });

      const view = getByTestId(wrapper, "WrapperView");
      act(() => view.props.navigator.push(rivers.A1234));
      act(() => view.props.navigator.push(rivers.C0987));

      it("returns to the previous entry in the stack which is not a hook", () => {
        expect(view.props.state.stack.mainStack).toHaveLength(3);

        expect(view.props.navigator).toHaveProperty(
          "currentRoute",
          "/river/B4567/river/A1234/river/C0987"
        );

        expect(view.props.navigator.screenData).toMatchObject({
          ...rivers.C0987,
          targetScreen: rivers.C0987,
        });

        act(() => view.props.navigator.goBack());

        expect(view.props.state.stack.mainStack).toHaveLength(2);

        expect(view.props.navigator).toHaveProperty(
          "currentRoute",
          "/river/B4567/river/A1234"
        );

        expect(view.props.navigator.screenData).toMatchObject({
          ...rivers.A1234,
          targetScreen: rivers.A1234,
        });
      });
    });
  });

  describe("modal feature", () => {
    it("sets the modalState when opening a modal", () => {
      act(() => {
        wrapper = renderContextConsumer();
      });

      const view = getByTestId(wrapper, "WrapperView");
      act(() => view.props.navigator.openModal({ item: rivers.A1234 }));
      expect(view.props.navigator.get).toMatchSnapshot();
    });

    it("removes the visible flag when dismissing a modal", () => {
      act(() => {
        wrapper = renderContextConsumer();
      });

      const view = getByTestId(wrapper, "WrapperView");
      act(() => view.props.navigator.openModal({ item: rivers.A1234 }));
      expect(view.props.navigator.getModalState()).toMatchSnapshot();
      act(() => view.props.navigator.dismissModal());
      expect(view.props.navigator.getModalState().visible).toBeFalsy();
      expect(view.props.navigator.getModalState()).toMatchSnapshot();
    });

    it("should reset state to initial when dismissing a modal ( closeModalBottomSheet ) opened by openModalBottomSheet", () => {
      act(() => {
        wrapper = renderContextConsumer();
      });

      const view = getByTestId(wrapper, "WrapperView");

      act(() =>
        view.props.navigator.openModalBottomSheet({
          ModalBottomSheetContent: rivers.A1234,
          modalBottomSheetContentProps: { some: "props" },
        })
      );

      expect(view.props.navigator.getModalState()).toMatchSnapshot();
      act(() => view.props.navigator.closeModalBottomSheet());
      expect(view.props.navigator.getModalState()).toMatchSnapshot();
    });
  });
});
