import * as React from "react";
import * as R from "ramda";

import { sendLaunchEvent } from "@applicaster/zapp-react-native-utils/analyticsUtils";
import { loadAppData } from "@applicaster/zapp-react-native-redux/AppData";
import {
  setAppLaunched,
  setAppReady,
} from "@applicaster/zapp-react-native-redux/appState";
import { isAndroidPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";

import { networkStatusReady } from "../NetworkStatusProvider/networkStatusReady";
import { updateSessionNumber } from "../SessionCounter/sessionCounter";

import { coreAppLogger } from "../logger";
import { Dispatch } from "@reduxjs/toolkit";
import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
import { requiredSessionKeys } from "../../const";
import { initializeActionSetters, useActionSetters } from "../ActionSetters";

const logger = coreAppLogger.addSubsystem("AppLifeCycleManager");

const isAndroid = isAndroidPlatform();

type Props = {
  dispatch: Dispatch;
  testCallback: () => void;
  plugins: QuickBrickPlugin[];
  appSettings: { runtimeConfigurationUrls: { [key in string]: string } };
};

export function appLifeCycleManager(appLoader, QuickBrickManager) {
  return function appLifeCycleManagerComponent(Component: ReactComponent<any>) {
    // this function is not called until React View is mounted to activity(ie quickBrickReady event sent)
    return function AppLifeCycleManagerRenderer(props: Props) {
      const actionsInitialStateSetters = useActionSetters();
      // using useLayoutEffect to call the loader just after the DOM render to mimic componentDidMount timing

      async function setUpdateSessionNumber(results: unknown[]) {
        await updateSessionNumber();

        logger.debug({
          message: "Loaders - Completed 1 - updateSessionNumber",
          data: {
            results,
          },
        });
      }

      function sendAnalyticsLaunchEvent(results: any) {
        const appDataResults = R.find(
          R.compose(R.has("payload"), R.defaultTo({}))
        )(results || []);

        logger.debug({
          message: "Loaders - Completed 2 - sendLaunchEvent",
          data: {
            appDataResults,
          },
        });

        sendLaunchEvent();
      }

      async function prepareActionSetters() {
        await initializeActionSetters(actionsInitialStateSetters);

        logger.debug({
          message: "Loaders - Completed 3 - initializeActionSetters",
        });
      }

      /**
       * Quick util that checks session storage for required context keys for all platforms
       * And logs the keys missing a value
       */
      async function checkStoredContextKeys() {
        // Calls all items in default applicaster namespace
        const allItems = await sessionStorage.getAllItems();

        // Array composed of any of the missing items that are required
        const missingRequiredItems = requiredSessionKeys.filter((key) =>
          isEmptyOrNil(allItems[key])
        );

        if (missingRequiredItems.length > 0) {
          logger.error({
            message: "Missing required context keys in session storage",
            data: { missingRequiredItems },
          });
        }
      }

      function makeAppReady() {
        const { dispatch, testCallback } = props;

        dispatch(setAppReady());
        dispatch(setAppLaunched());

        logger.debug({
          message: "Loaders - Completed 4 - setAppReady, setAppLaunched",
        });

        // TODO: Must be later
        logger.debug({
          message: "AllowScriptEvaluation.UIComponent",
        });

        logger.debug({
          message: "AllowScriptEvaluation.Screen",
        });

        if (!isAndroid) {
          QuickBrickManager.sendQuickBrickEvent("quickBrickReady");
        }

        if (process.env.NODE_ENV === "test") {
          testCallback();
        }
      }

      // TODO: Move to login plugin utils
      const refreshLoginPlugins = async (plugins: QuickBrickPlugin[]) => {
        try {
          // TODO: use plugin type constants PluginType
          const promiseProtocols = plugins
            .filter(
              (plugin: any) =>
                plugin.type === "login" && plugin.module?.getLoginProtocol
            )
            .map((plugin: any) => plugin.module?.getLoginProtocol?.());

          const loginProtocols: LoginProtocol[] =
            await Promise.all(promiseProtocols);

          // Await all does not work here
          for (const protocol of loginProtocols) {
            if (await protocol.isLoggedIn()) {
              await protocol.refresh();
            }
          }

          logger.debug({
            message: `AppLifeCycleManager: Login plugins refreshed successfully: ${loginProtocols.length}`,
          });
        } catch (error) {
          logger.error({
            message: `AppLifeCycleManager: Login plugins refresh failed: ${error.message}`,
          });
        }
      };

      const prepareApp = async () => {
        // eslint-disable-next-line unused-imports/no-unused-vars
        const { dispatch, testCallback, ...state } = props;

        try {
          const getState = () => state;

          const loaders = [
            appLoader(dispatch, getState),
            dispatch<any>(loadAppData()),
            networkStatusReady(),
          ];

          const results = await Promise.all(loaders);
          await setUpdateSessionNumber(results);

          await refreshLoginPlugins(props.plugins);
          sendAnalyticsLaunchEvent(results);
          await prepareActionSetters();

          logger.debug({
            message: `AppLifeCycleManager:  Loaders Successfully Finished, the app is ready to be presented${
              isAndroid ? "" : ", sending to naive event: quickBrickReady"
            }`,
          });
        } catch (error) {
          logger.warning({
            message: `AppLifeCycleManager: A loading operation failed: ${error.message}`,
            data: { error },
          });
        }

        makeAppReady();
        checkStoredContextKeys();
      };

      React.useLayoutEffect(() => {
        prepareApp();
      }, []);

      return <Component {...props} />;
    };
  };
}
