import React, { useEffect, useRef, useState } from 'react';
import {
  ActivityIndicator,
  Linking,
  Platform,
  StyleSheet,
  View,
  Modal,
  ScrollView,
  Dimensions,
  TouchableOpacity,
  Image,
  TouchableWithoutFeedback,
  Text,
  NativeModules,
  NativeEventEmitter,
} from 'react-native';
import WebView, { type WebViewMessageEvent } from 'react-native-webview';
import { getContact } from '../functions';
import Share from 'react-native-share';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { encryptUrlParams } from '../utils/encryption';

const OmnipayActivity = NativeModules.OmnipayActivity || {};

type AnalyticsConfig = {
  /**
   * Callback to send analytics events back to the parent app.
   * Receives the event name and optional payload.
   */
  onEvent: (eventName: string, payload?: Record<string, unknown>) => void;
  /**
   * Optional whitelist of events to forward.
   * If omitted or empty, all events will be forwarded.
   */
  allowedEvents?: string[];
};

type OmnipayProviderProps = {
  publicKey: string;
  env: 'dev' | 'prod';
  color: string;
  children: React.ReactElement | React.ReactElement[];
  analyticsConfig?: AnalyticsConfig;
};

type PosTransactionType = {
  amount: number;
  purchaseType: 'PURCHASE' | 'KEY EXCHANGE';
  color: string;
  print: boolean;
  rrn: string;
  stan: string;
  terminalId: string;
};

type PostMessage = {
  [key: string]: unknown;
};

type WebviewMessageData = {
  dataKey: string;
  dataValue: unknown;
};

type MessageHandler = (dataValue: unknown) => void | Promise<void>;

type Status = 'error' | 'loading' | 'success';

type InitiateBillsType = {
  phoneNumber: string;
  onClose?: () => void;
  metadata?: Record<string, string | number | boolean>;
};

type InitiateWalletType = {
  phoneNumber?: string;
  customerRef?: string;
  userRef?: string;
  usesPaylater?: boolean;
  usesPromo?: boolean;
  usesAirtimeData?: boolean;
  usesTransfer?: boolean;
  usesBills?: boolean;
  usesPos?: boolean;
  onClose?: () => void;
  promoBalanceOffset?: number;
  deviceId?: string;
  deviceName?: string;
  hideWalletTransfer?: boolean;
  isBvnValidationRequired?: boolean;
  walletTab?: 'Paylater' | 'Account' | 'Omoni';
  sessionId?: string;
  kycStatus?: 'verified' | 'unverified';
  launchPage?: string;
  promoName?: string;
  metadata?: Record<string, string | number | boolean>;
};

export type OmnipayContextType = {
  initiateBills: ({ phoneNumber, onClose }: InitiateBillsType) => void;
  initiateWallet: ({
    phoneNumber,
    customerRef,
    userRef,
    onClose,
    usesPaylater,
    usesPromo,
    usesAirtimeData,
    usesTransfer,
    usesBills,
    usesPos,
    promoBalanceOffset,
    deviceId,
    deviceName,
    hideWalletTransfer,
    isBvnValidationRequired,
    walletTab,
    sessionId,
    launchPage,
    kycStatus,
    promoName,
    metadata,
  }: InitiateWalletType) => void;
};

let defaultValue = {
  initiateBills: () => null,
  initiateWallet: () => null,
};

export const OmnipayContext = React.createContext<OmnipayContextType | null>(
  defaultValue
);

export const OmnipayProvider = ({
  children,
  publicKey,
  env,
  color,
  analyticsConfig,
}: OmnipayProviderProps) => {
  const [webviewStatus, setWebviewStatus] = useState<Status>('loading');
  const [isVisible, setIsVisible] = useState(false);
  const webviewRef = useRef<WebView>(null);
  const visibilityRef = useRef<boolean>(isVisible);
  const webHost = getWebHost();
  const [webviewUrl, setWebviewUrl] = useState(webHost);
  const showWebview = webviewUrl.includes('view') && isVisible;
  const [containerOffset, setContainerOffset] = useState(30);
  const isValidEnv = ['prod', 'dev'].includes(env);
  const isValidColor = color.length > 2;
  const onCloseRef = useRef<(() => void) | undefined>(undefined);
  const [canUsePos, setCanUsePos] = useState(false);

  const allowedEvents = React.useMemo(
    () =>
      analyticsConfig?.allowedEvents && analyticsConfig.allowedEvents.length > 0
        ? new Set(analyticsConfig.allowedEvents)
        : null,
    [analyticsConfig?.allowedEvents]
  );

  useEffect(() => {
    checkPaymentApp();
  }, []);

  useEffect(() => {
    visibilityRef.current = isVisible;
  }, [isVisible]);

  useEffect(() => {
    if (canUsePos) {
      const eventEmitter = new NativeEventEmitter(OmnipayActivity);
      eventEmitter.addListener('OmnipayEvent', (event) => {
        console.log('native event', event);
      });
    }
  }, [canUsePos]);

  async function checkPaymentApp() {
    try {
      if (Platform.OS === 'android') {
        const isInstalled = await OmnipayActivity.isPackageInstalled(
          'com.horizonpay.sample'
        );
        if (isInstalled) {
          setCanUsePos(true);
        }
      }
    } catch (error) {}
  }

  async function startPosTransaction({
    amount,
    purchaseType,
    print,
    rrn,
    stan,
    terminalId,
  }: PosTransactionType) {
    try {
      if (Platform.OS === 'android') {
        let result = '';

        if (purchaseType === 'KEY EXCHANGE') {
          const isKeyExchanged = await AsyncStorage.getItem('isKeyExchanged');
          if (!isKeyExchanged) {
            result = await OmnipayActivity.initiateHorizonTransaction(
              amount,
              purchaseType,
              color,
              print,
              rrn,
              stan,
              terminalId
            );
            if (
              terminalId &&
              result &&
              result.toLowerCase().includes('-message-success')
            ) {
              await AsyncStorage.setItem('isKeyExchanged', terminalId);
            }
            postMessage({
              dataKey: 'onPosKeyExchanged',
              dataValue: result,
            });
          }
        } else {
          result = await OmnipayActivity.initiateHorizonTransaction(
            amount,
            purchaseType,
            color,
            print,
            rrn,
            stan,
            terminalId
          );
          postMessage({
            dataKey: 'onPosTransactionSuccess',
            dataValue: result,
          });
        }
      }
    } catch (error) {
      console.log(error);
      postMessage({
        dataKey: 'onPosTransactionFailure',
        dataValue: '',
      });
    }
  }

  function getWebviewStyle() {
    if (!showWebview) {
      return { opacity: 0, height: 0, width: 0, flex: 0 };
    }
    return styles.webview;
  }

  function getWebHost() {
    if (env === 'dev') {
      return 'https://websdk-dev.ompy.ng/';
    }
    return 'https://websdk.ompy.ng/';
  }

  const onWebviewMount = `
        (function() {
          window.nativeOs = "${Platform.OS}";
          true;
        })();
        true;
      `;

  function postMessage(data: PostMessage) {
    if (!webviewRef.current) {
      return;
    }
    try {
      webviewRef.current.postMessage(JSON.stringify(data));
    } catch (error) {}
  }

  const handleChooseContact = async () => {
    const contactDetails = await getContact();
    postMessage({
      dataKey: 'contactSelected',
      dataValue: contactDetails,
    });
  };

  const handleOpenLink = (dataValue: unknown) => {
    if (typeof dataValue === 'string') {
      Linking.openURL(dataValue);
    }
  };

  const handleModalOpen = () => {
    setContainerOffset(0);
  };

  const handleModalClosed = (dataValue: unknown) => {
    setContainerOffset(30);
    if (dataValue === 'closeSdk') {
      closeWebview();
    }
  };

  const handleCloseSdk = () => {
    closeWebview();
  };

  const handleShareReceipt = async (receiptData: unknown) => {
    if (typeof receiptData !== 'string') return;

    try {
      const { fileName = '', image = '' } = JSON.parse(receiptData);
      await Share.open({
        url: image,
        filename: Platform.OS === 'android' ? fileName : `${fileName}.jpg`,
      });
    } catch (error) {
      console.log('Error sharing receipt:', error);
    }
  };

  const handlePosTransaction = (dataValue: unknown) => {
    if (typeof dataValue !== 'string') return;

    try {
      startPosTransaction(JSON.parse(dataValue));
    } catch (error) {
      console.warn('Error parsing POS transaction data:', error);
    }
  };

  const handleAnalyticsEvent = (dataValue: unknown) => {
    if (!analyticsConfig?.onEvent) {
      return;
    }

    try {
      const payload =
        typeof dataValue === 'string' ? JSON.parse(dataValue) : dataValue;

      const { eventName, eventProperties } = payload;

      if (typeof eventName === 'string' && eventName.trim()) {
        const shouldForward = !allowedEvents || allowedEvents.has(eventName);

        if (shouldForward) {
          analyticsConfig.onEvent(eventName, eventProperties || {});
        }
      } else {
        console.warn('Omnipay analytics: Invalid event name', eventName);
      }
    } catch (error) {
      console.warn('Omnipay analytics error:', error);
    }
  };

  /**
   * Maps message keys to handlers for O(1) lookup and easy extensibility.
   * Add new message types here instead of chaining if statements.
   */
  const messageHandlers: Record<string, MessageHandler> = {
    chooseContact: handleChooseContact,
    openLink: handleOpenLink,
    modalOpen: handleModalOpen,
    modalClosed: handleModalClosed,
    closeSdk: handleCloseSdk,
    shareReceipt: handleShareReceipt,
    startPosTransaction: handlePosTransaction,
    analyticsEvent: handleAnalyticsEvent,
  };

  async function onWebviewMessage(e: WebViewMessageEvent) {
    try {
      if (!e.nativeEvent?.data) {
        return;
      }

      const eventData: WebviewMessageData = JSON.parse(e.nativeEvent.data);
      const { dataKey, dataValue } = eventData;

      const handler = messageHandlers[dataKey];
      if (handler) {
        await handler(dataValue);
      }
    } catch (error) {
      console.warn('Error handling webview message:', error);
    }
  }

  const _initiateBills = ({
    phoneNumber,
    onClose,
    metadata = {},
  }: InitiateBillsType) => {
    if (typeof phoneNumber === 'string' && phoneNumber.length >= 10) {
      // Encrypt params to prevent exposure of sensitive data in URL/logs
      const params = {
        theme: color,
        view: 'bills',
        publicKey: publicKey,
        phoneNumber: phoneNumber,
        ...metadata,
      };
      const encryptedPayload = encryptUrlParams(params);
      const webUrl = `${webHost}?cfg=${encryptedPayload}&view=bills`;
      setWebviewUrl(webUrl);
      setIsVisible(true);
      onCloseRef.current = onClose;
      return;
    }
    console.warn('Omnipay error: Invalid phone number');
  };

  const _initiateWallet = ({
    phoneNumber = '',
    customerRef = '',
    userRef = '',
    onClose,
    usesPaylater = false,
    usesPromo = false,
    usesAirtimeData = false,
    usesTransfer = false,
    usesBills = false,
    usesPos = false,
    promoBalanceOffset = 0,
    deviceId = '',
    deviceName = '',
    hideWalletTransfer = false,
    isBvnValidationRequired = false,
    walletTab = 'Account',
    sessionId = '',
    kycStatus,
    launchPage = 'wallet',
    promoName = '',
    metadata = {},
  }: InitiateWalletType) => {
    // Guard against race conditions when wallet is already initializing/visible
    if (visibilityRef.current) {
      return;
    }
    const isPhoneNumberValid = !!phoneNumber && phoneNumber.length >= 10;
    const isValidCustomerRef = !!customerRef && !!customerRef.trim();
    const isValidUserRef = !!userRef && !!userRef.trim();
    const usesNativeShare = true;

    if (isPhoneNumberValid || isValidCustomerRef || isValidUserRef) {
      // Encrypt params to prevent exposure of PII and API keys in URL/logs
      const params = {
        theme: color,
        view: 'wallet',
        publicKey: publicKey,
        phoneNumber: phoneNumber,
        usesPaylater: usesPaylater,
        usesPromo: usesPromo,
        usesAirtimeData: usesAirtimeData,
        usesTransfer: usesTransfer,
        usesBills: usesBills,
        usesPos: usesPos,
        customerRef: customerRef,
        userRef: userRef,
        promoBalanceOffset: promoBalanceOffset,
        deviceId: deviceId,
        deviceName: deviceName,
        hideWalletTransfer: hideWalletTransfer,
        bvnRequired: isBvnValidationRequired,
        usesNativeShare: usesNativeShare,
        isPosEnabled: canUsePos,
        walletTab: walletTab,
        sessionId: sessionId,
        kycStatus: kycStatus || '',
        launchPage: launchPage,
        promoName: promoName,
        ...metadata,
      };
      const encryptedPayload = encryptUrlParams(params);
      const webUrl = `${webHost}?cfg=${encryptedPayload}&view=wallet`;
      setWebviewUrl(webUrl);
      setIsVisible(true);
      onCloseRef.current = onClose;
      return;
    }
    console.warn('Omnipay error: Invalid phone number');
  };

  function closeWebview() {
    setIsVisible(false);
    if (onCloseRef.current && typeof onCloseRef.current === 'function') {
      onCloseRef.current();
      onCloseRef.current = undefined;
    }
  }

  function reloadWebview() {
    if (webviewRef.current) {
      webviewRef.current.reload();
    }
  }

  const webviewStyle = getWebviewStyle();
  const isPropsValid = isValidColor && !!publicKey && isValidEnv;
  const isWalletView = webviewUrl.includes('view=wallet');

  return (
    <OmnipayContext.Provider
      value={{ initiateBills: _initiateBills, initiateWallet: _initiateWallet }}
    >
      {isPropsValid && (
        <>
          {webviewUrl.includes('view') && (
            <Modal visible={showWebview} style={styles.modal} transparent>
              <TouchableOpacity
                style={styles.backdrop}
                activeOpacity={1}
                onPress={closeWebview}
              >
                <View
                  style={[
                    styles.container,
                    // Dynamic padding based on modal state - inline style needed for animation
                    { paddingTop: isWalletView ? 0 : containerOffset },
                  ]}
                >
                  {containerOffset !== 0 &&
                    webviewStatus === 'success' &&
                    !isWalletView && (
                      <TouchableOpacity
                        style={styles.close}
                        onPress={closeWebview}
                      >
                        <Image
                          source={require('../assets/cancel.png')}
                          style={styles.closeIcon}
                        />
                      </TouchableOpacity>
                    )}

                  <ScrollView contentContainerStyle={styles.contentContainer}>
                    <TouchableWithoutFeedback>
                      <WebView
                        source={{
                          uri: webviewUrl,
                        }}
                        style={webviewStyle}
                        injectedJavaScriptBeforeContentLoaded={onWebviewMount}
                        onMessage={onWebviewMessage}
                        ref={webviewRef}
                        onLoadStart={() => {
                          setWebviewStatus('loading');
                        }}
                        domStorageEnabled={true}
                        originWhitelist={['*']}
                        allowsInlineMediaPlayback={true}
                        onLoadEnd={() => setWebviewStatus('success')}
                        renderError={() => (
                          <View style={StyleSheet.absoluteFillObject}>
                            <View style={styles.errorContainer}>
                              <Text style={styles.errorSubtitle}>
                                Unable to open your wallet. Please try again
                              </Text>
                              <TouchableOpacity
                                activeOpacity={0.8}
                                onPress={reloadWebview}
                                style={[
                                  styles.button,
                                  {
                                    backgroundColor: color,
                                    borderColor: color,
                                  },
                                ]}
                              >
                                <>
                                  <Text style={styles.buttonText}>Retry</Text>
                                </>
                              </TouchableOpacity>
                            </View>
                          </View>
                        )}
                      />
                    </TouchableWithoutFeedback>
                    {webviewStatus === 'loading' && showWebview && (
                      <TouchableWithoutFeedback>
                        <View style={styles.webviewLoader}>
                          <ActivityIndicator size="large" color={color} />
                        </View>
                      </TouchableWithoutFeedback>
                    )}
                  </ScrollView>
                </View>
              </TouchableOpacity>
            </Modal>
          )}
        </>
      )}
      {children}
    </OmnipayContext.Provider>
  );
};

const styles = StyleSheet.create({
  hide: {
    display: 'none',
  },
  full: {
    flex: 1,
    width: '100%',
    height: '100%',
  },
  webview: {
    flex: 1,
    width: '100%',
    height: Dimensions.get('window').height - 40,
    backgroundColor: 'white',
    borderTopRightRadius: 20,
    borderTopLeftRadius: 20,
  },
  webviewLoader: {
    zIndex: 3,
    backgroundColor: 'white',
    alignItems: 'center',
    justifyContent: 'center',
    flex: 1,
    width: '100%',
    height: '100%',
    position: 'absolute',
    top: 0,
    left: 0,
    borderTopRightRadius: 20,
    borderTopLeftRadius: 20,
  },
  backdrop: {
    backgroundColor: 'rgba(0,0,0,0.48)',
    flex: 1,
    justifyContent: 'flex-end',
    position: 'relative',
    height: '100%',
  },
  container: {
    backgroundColor: 'white',
    borderTopRightRadius: 20,
    borderTopLeftRadius: 20,
    maxHeight: Dimensions.get('window').height - 40,
    flex: 1,
    position: 'relative',
    ...(Platform.OS === 'android' && { overflow: 'hidden' }),
  },
  modal: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.48)',
    height: '100%',
    width: '100%',
  },
  close: {
    position: 'absolute',
    top: 10,
    right: 10,
    backgroundColor: 'white',
    height: 24,
    width: 24,
    borderRadius: 1000,
    alignItems: 'center',
    justifyContent: 'center',
    zIndex: 2,
  },
  closeIcon: {
    height: 12,
    width: 12,
  },
  contentContainer: {
    flex: 1,
    height: Dimensions.get('window').height - 40,
    position: 'relative',
    borderTopRightRadius: 20,
    borderTopLeftRadius: 20,
  },
  testContent: {
    paddingTop: 30,
    paddingLeft: 16,
  },
  testTwoContent: {
    paddingTop: 10,
    paddingLeft: 16,
  },
  errorSubtitle: {
    textAlign: 'center',
    fontSize: 14,
    color: '#5e7079',
    marginBottom: 20,
    paddingHorizontal: 8,
    marginTop: 14,
  },
  errorContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    width: Dimensions.get('window').width,
    height: Dimensions.get('window').height,
    zIndex: 2,
    backgroundColor: 'white',
  },
  retryButton: {
    minWidth: 160,
    marginHorizontal: 'auto',
  },
  button: {
    borderRadius: 6,
    paddingHorizontal: 12,
    paddingVertical: 14,
    borderWidth: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  buttonText: { color: 'white', fontSize: 16, paddingHorizontal: 30 },
});
