/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useEffect, useImperativeHandle, useRef, useState } from 'react';
import { Linking, Platform, View } from 'react-native';
import { useSelector } from 'react-redux';
import { WebView, WebViewMessageEvent } from 'react-native-webview';
import { useSafeAreaDimensions } from '../hooks/useSafeAreaDimensions';
import html, { HTML_PLACEHOLDER } from './html';
import {
  RequestRefreshEvent,
  UnitComponentsMessage,
  RequestDownloadEvent,
  RequestOpenLinkEvent,
  MultiFactorAuthenticationFinishedEvent,
} from '../messages/webMessages/unitComponentsMessages';
import { getHtmlBody } from '../scripts/html/bodyHtml';
import { fetchUnitScript } from '../unitComponentsSdkManager/UnitComponentsSdk.api';
import { UnitComponentsSDK } from '../unitComponentsSdkManager/UnitComponentsSdkManager';
import type { WebViewMessage } from '../messages/webMessages';

import { getInfoParams, handleRequestDownload, injectEventToContinue } from './WebComponent.utils';
import { PresentationMode, WebComponentType } from '../types/internal/webComponent.types';
import { getFontFacesString } from '../scripts/html/fontFaces';
import type { RootState } from '../store';
import { eventBus } from '../utils/eventBus';
import AppInfo from '../utils/AppInfo';
import UNStoreManagerHelper from '../nativeModulesHelpers/UNStoreModuleHelper/UNStoreModuleHelper';
import { UserDataKeys } from '../types/internal/unitStore.types';
import { useEventListener } from '../hooks/useEventListener';
import { setItemInWindowUnitStore } from '../utils/windowUnitStore';

export interface WebComponentProps {
  type: WebComponentType;
  presentationMode?: PresentationMode,
  params?: string;
  theme?: string;
  language?: string;
  onMessage?: (message: WebViewMessage) => void;
  script?: string;
  isScrollable?: boolean,
  nestedScrollEnabled?: boolean,
  handleScroll?: (event: any) => void,
  windowParams?: string;
}

export const WebComponent = React.forwardRef<WebView, WebComponentProps>(function WebComponent(props, webOutRef) {
  const unitScript = useSelector((state: RootState) => state.configuration.unitScript);
  const globalTheme = useSelector((state: RootState) => state.configuration.theme);
  const globalLanguage = useSelector((state: RootState) => state.configuration.language);
  const customerToken = useSelector((state: RootState) => state.configuration.customerToken);
  const [sourceHtml, setSourceHtml] = useState<string | null>(null);
  const [baseName, setBaseName] = useState<string>();
  const [androidPackageName, setAndroidPackageName] = useState<string>();
  const [infoParams, setInfoParams] = useState<{ [key: string]: string }>({});

  const webRef = useRef<WebView>(null);
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  useImperativeHandle(webOutRef, () => webRef.current!);

  useEffect(() => {
    const getAppName = async () => {
      // For iOS, we extend the app name from the HTML to display a prettier access request message.
      // On Android, there is no request message sent from HTML.
      try {
        if (Platform.OS == 'ios') {
          const name = await AppInfo.getAppName();
          setBaseName(name.replace(/ /g, '-'));
        } else { // android
          setBaseName('unit');
          const packageName = await AppInfo.getAppIdentifier();
          setAndroidPackageName(packageName);
        }
      } catch (error: any) {
        console.error(error);
      }
    };

    const updateInfoParams = async () => {
      const infoParams = await getInfoParams();
      setInfoParams(infoParams);
    };

    getAppName();
    updateInfoParams();
  }, []);

  useEffect(() => {
    if (!unitScript) {
      fetchUnitScript();
      return;
    }

    const updateSourceHTML = async () => {
      const componentCurrentTheme = props.theme ?? globalTheme;
      const componentCurrentLanguage = props.language ?? globalLanguage;

      const themeParam = componentCurrentTheme ? ` theme="${componentCurrentTheme}"` : '';
      const languageParam = componentCurrentLanguage ? ` language="${componentCurrentLanguage}"` : '';

      const componentsRequiresExternalTokenInsertion = [WebComponentType.whiteLabelApp];
      const customerTokenParam = (componentsRequiresExternalTokenInsertion.includes(props.type))
        ? ''
        : `customer-token="${customerToken}"\n`;

      const componentParams = customerTokenParam + (props.params || '') + themeParam + languageParam;

      const fontFaces = getFontFacesString(UnitComponentsSDK.getFonts(), UnitComponentsSDK.iosFontBase64Map);

      const windowInfoParams = `window.UnitMobileSDKConfig['info'] = ${JSON.stringify(infoParams)};`;
      const plaidRedirectUriParam = (Platform.OS == 'ios' && UnitComponentsSDK.helpers.redirectUri) ? `window.UnitMobileSDKConfig['plaidRedirectUri'] = '${UnitComponentsSDK.helpers.redirectUri}/plaid';` : '';
      const androidPackageNameParam = androidPackageName ? `window.UnitMobileSDKConfig.androidPackageName='${androidPackageName}';` : '';
      const unitSessionIdParam = `window.UnitSessionStore.unitSessionId = '${UnitComponentsSDK.helpers.unitSessionId}';`;

      const unitVerifiedCustomerToken = await UNStoreManagerHelper.getValue(UserDataKeys.unitVerifiedCustomerToken);
      const unitAppFormVerifiedToken = await UNStoreManagerHelper.getValue(UserDataKeys.unitApplicationFormVerifiedToken);
      const windowVerifiedCustomerToken = unitVerifiedCustomerToken ? `window.UnitStore['${UserDataKeys.unitVerifiedCustomerToken}'] = '${unitVerifiedCustomerToken}';` : '';
      const windowAppFormVerifiedToken = unitAppFormVerifiedToken ? `window.UnitStore['${UserDataKeys.unitApplicationFormVerifiedToken}'] = '${unitAppFormVerifiedToken}';` : '';

      const windowParams = `${windowInfoParams} ${unitSessionIdParam} ${plaidRedirectUriParam} ${androidPackageNameParam} ${windowVerifiedCustomerToken} ${windowAppFormVerifiedToken} ${props.windowParams || ''}`;

      let newHtml = html.replace(HTML_PLACEHOLDER.BODY, getHtmlBody(props.type.valueOf(), componentParams, props.presentationMode));
      newHtml = newHtml.replace(HTML_PLACEHOLDER.FONT_FACES, fontFaces);
      newHtml = newHtml.replace(HTML_PLACEHOLDER.SCRIPT_FROM_NATIVE, props.script || '');
      newHtml = newHtml.replace(HTML_PLACEHOLDER.WINDOW_PARAMS, windowParams);
      setSourceHtml(newHtml);
    };

    updateSourceHTML();

  }, [props.params, unitScript, props.presentationMode, props.script, props.windowParams, globalTheme, globalLanguage, customerToken, infoParams, androidPackageName]);

  // Listen and update the live webComponents
  const handleMultiFactorAuthFinished = (data: MultiFactorAuthenticationFinishedEvent) => {
    setItemInWindowUnitStore(webRef.current, data.tokenStoreKey as UserDataKeys, data.tokenString);
    injectEventToContinue(webRef.current, {
      parentInstanceId: data.parentInstanceId,
      eventToContinue: data.eventToContinue
    });
  };

  useEventListener({ busEventKey: UnitComponentsMessage.UNIT_MULTI_FACTOR_AUTH_FINISHED, action: handleMultiFactorAuthFinished });

  const onMessage = (e: WebViewMessageEvent) => {
    const message = JSON.parse(e.nativeEvent.data) as WebViewMessage;
    switch (message.type) {
      case UnitComponentsMessage.UNIT_REQUEST_REFRESH:
        message.details &&
          eventBus.emit(
            UnitComponentsMessage.UNIT_REQUEST_REFRESH,
            message.details as RequestRefreshEvent
          );
        break;
      case UnitComponentsMessage.UNIT_REQUEST_OPEN_LINK:
        // eslint-disable-next-line no-case-declarations
        const { href } = (message.details as RequestOpenLinkEvent);
        Linking.openURL(href);
        break;

      case UnitComponentsMessage.UNIT_REQUEST_DOWNLOAD:
        if (message.details) {
          handleRequestDownload(message.details as RequestDownloadEvent).then((result) => {
            const urlArg = JSON.stringify(result.url);
            const errorArg = result.error ? JSON.stringify(result.error) : 'undefined';
            webRef.current?.injectJavaScript(`dispatchOnDownload(${urlArg}, ${errorArg})`);
            eventBus.emit(UnitComponentsMessage.UNIT_REQUEST_CLOSE_FLOW, {});
          });
        }
        break;
      case UnitComponentsMessage.UNIT_MULTI_FACTOR_AUTH_FINISHED:
        if (message.details) {
          const data = message.details as MultiFactorAuthenticationFinishedEvent;
          UNStoreManagerHelper.saveValue(data.tokenStoreKey as UserDataKeys, data.tokenString);
          // update existing components - namely, the other webComponents will update their window as well as this webComponent
          eventBus.emit(
            UnitComponentsMessage.UNIT_MULTI_FACTOR_AUTH_FINISHED,
            data
          );
        }
        props.onMessage && props.onMessage(message);
        break;
      case UnitComponentsMessage.UNIT_UNAUTHORIZED_TOKEN:
        UnitComponentsSDK.cleanUserData();
        break;
      default:
        props.onMessage && props.onMessage(message);
    }
  };

  if (!sourceHtml) return null;

  const _onScroll = (event: any) => {
    if (props.handleScroll) {
      props.handleScroll(event);
    }
  };

  if (!baseName) {
    return null;
  }

  const isInheritMode = props.presentationMode === PresentationMode.Inherit;
  const { effectiveBottomInset } = useSafeAreaDimensions();
  const needsBottomPadding = isInheritMode && effectiveBottomInset > 0;

  const webView = (
    <WebView
      ref={webRef}
      originWhitelist={['*']}
      mediaPlaybackRequiresUserAction={false}
      allowsInlineMediaPlayback={true}
      cacheEnabled={false}
      scrollEnabled={props.isScrollable}
      nestedScrollEnabled={props.nestedScrollEnabled}
      onScroll={_onScroll}
      overScrollMode='never'
      injectedJavaScript={unitScript}
      style={{ width: '100%', flex: 1, opacity: 0.99, backgroundColor: 'transparent' }}
      source={{ html: sourceHtml, baseUrl: `https://${baseName}` }}
      onMessage={onMessage}
      androidLayerType='hardware'
      webviewDebuggingEnabled={__DEV__}
      mediaCapturePermissionGrantType='grant'
    />
  );

  if (needsBottomPadding) {
    return (
      <View style={{ flex: 1, paddingBottom: effectiveBottomInset }}>
        {webView}
      </View>
    );
  }

  return webView;
});
