import * as React from "react";
import { View, Text } from "react-native";
import { waitFor } from "@testing-library/react-native";
import { renderWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";

let mocked_items: Record<string, any> = {};

import { appLifeCycleManager } from "../";

const mock_actions = {};

jest.mock("@applicaster/zapp-react-native-redux", () => ({
  ...jest.requireActual("@applicaster/zapp-react-native-redux"),
  AppData: {
    loadAppData: jest.fn(() => () => Promise.resolve()),
  },
}));

jest.mock(
  "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage",
  () => ({
    sessionStorage: {
      getAllItems: jest.fn(() => Promise.resolve(mocked_items)),
    },
  })
);

jest.mock("@applicaster/zapp-react-native-utils/reactHooks/actions", () => ({
  useActions: jest.fn(() => ({ actions: mock_actions })),
}));

jest.mock(
  "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage",
  () => ({
    localStorage: {
      ...jest.requireActual(
        "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage"
      ).localStorage,
      getAllItems: jest.fn(() => Promise.resolve(mocked_items)),
      setItem: jest.fn((key, value) => {
        mocked_items[key] = JSON.stringify(value);

        return Promise.resolve(true);
      }),
      getItem: jest.fn((key) => {
        if (mocked_items[key]) {
          if (typeof mocked_items[key] === "string") {
            return Promise.resolve(JSON.parse(mocked_items[key]));
          } else {
            return Promise.resolve(mocked_items[key]);
          }
        } else {
          return Promise.resolve([]);
        }
      }),
      __reset() {
        mocked_items = {};
      },
    },
  })
);

const Component = () => (
  <View>
    <Text>I'm a component</Text>
  </View>
);

const QuickBrickManager = {
  sendQuickBrickEvent: jest.fn(),
};

const LOADING_ERROR = new Error("loading failed");

const appLoader = jest.fn((_dispatch, _getState) => {
  if (_getState().shouldFail) return Promise.reject(LOADING_ERROR);

  return Promise.resolve();
});

const state = {
  plugins: {} as QuickBrickPlugin[],
  appSettings: { runtimeConfigurationUrls: { rivers: "http://rivers.url" } },
};

const QUICK_BRICK_READY_EVENT = "quickBrickReady";

describe("appLifeCycleManager", () => {
  const decorator = appLifeCycleManager(appLoader, QuickBrickManager);
  const DecoratedComponent = decorator(Component);

  beforeEach(() => {
    appLoader.mockClear();
    QuickBrickManager.sendQuickBrickEvent.mockClear();
  });

  it("returns a function to decorate a component", () => {
    expect(decorator).toBeDefined();
  });

  it("runs the loaders, then sends quickBrickReady event", async () => {
    const testCallback = jest.fn(() => {});

    renderWithProviders(
      <DecoratedComponent {...state} testCallback={testCallback} />,
      state
    );

    await waitFor(() => {
      return expect(testCallback).toHaveBeenCalled();
    });

    const actions = appStore.testStore.getActions();

    expect(actions[0]).toMatchObject({ type: "SET_APP_READY" });

    expect(QuickBrickManager.sendQuickBrickEvent).toHaveBeenCalledWith(
      QUICK_BRICK_READY_EVENT
    );

    expect(appLoader).toHaveBeenCalled();
  });

  it("sends the quickBrickReady event when a loader doesn't resolves", async () => {
    const testCallback = jest.fn(() => {});

    renderWithProviders(
      <DecoratedComponent {...state} shouldFail testCallback={testCallback} />,
      state
    );

    const actions = appStore.testStore.getActions();

    await waitFor(() => {
      return expect(testCallback).toHaveBeenCalled();
    });

    expect(actions[0]).toMatchObject({ type: "SET_APP_READY" });
    expect(actions[1]).toMatchObject({ type: "SET_APP_LAUNCHED" });

    expect(QuickBrickManager.sendQuickBrickEvent).toHaveBeenCalledWith(
      QUICK_BRICK_READY_EVENT
    );

    expect(testCallback).toHaveBeenCalled();

    expect(appLoader).toHaveBeenCalled();
  });
});
