// noinspection JSIgnoredPromiseFromCall,JSVoidFunctionReturnValueUsed

import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Image, ImageSourcePropType, StyleSheet, TouchableOpacity, View } from 'react-native';
import { Slider } from '../react-native-awesome-slider/src-change';
import { useSharedValue } from 'react-native-reanimated';
import { GestureHandlerRootView, PanGestureHandlerEventPayload } from 'react-native-gesture-handler';
//region import Crypto
// import * as Crypto from 'expo-crypto';
import 'react-native-get-random-values';
import CryptoJS from 'crypto-js';

const Crypto: any = (global as any).crypto;
//endregion

/**
 * AES 加密
 * @param plaintext
 * @param key 密钥，必须是 16、24 或 32 位
 */
const encrypt = (plaintext: string, key: string) => {
  return CryptoJS.AES.encrypt(plaintext, key).toString();
};
const decrypt = (ciphertext: string, key: string) => {
  return CryptoJS.AES.decrypt(ciphertext, key).toString(CryptoJS.enc.Utf8);
};

export const AESUtil = {
  encrypt,
  decrypt,
};

/**
 * 返回 [minValue, maxValue] 的随机数, 而不是百分比
 * @param minValue
 * @param maxValue
 */
export const getRandomValueBetween = (minValue: number, maxValue: number): number => {
  const randomPercent = (Crypto.getRandomValues(new Uint8Array(1))[0] as number) / 256.0;
  let randomValue = minValue + (maxValue - minValue) * randomPercent;
  randomValue = Math.min(Math.max(minValue, randomValue), maxValue);
  return randomValue;
};

export const sum = (x: number, y: number) => x + y;
export const square = (x: number) => x * x;

export interface SliderAllStatus {
  default: SliderItemStatus;
  moving: SliderItemStatus;
  success: SliderItemStatus;
  failure: SliderItemStatus;
}

export interface SliderItemStatus {
  backgroundColor: string;
  borderColor: string;
  sliderText?: string;
  thumbBackgroundColor: string;
  thumbImageSource: ImageSourcePropType;
}

export interface BubbleSource {
  behind: ImageSourcePropType;
  above: ImageSourcePropType;
}

export type LocalBubbleBehindType = 'always' | 'never' | 'showAfterTouchBegin';

export interface CaptchaProps {
  /**
   * 禁用滑块滑动, default false
   */
  disableSlider?: boolean;
  localBubbleBehindType?: LocalBubbleBehindType;
  /**
   * 校验的误差范围内算成功 default 3
   */
  maxOffset?: number;
  /**
   * 滑块尺寸 default 38
   */
  thumbSize?: number;
  /**
   * 弹窗尺寸 default 49
   */
  bubbleSize?: number;
  /**
   * 底图宽度 default 292
   */
  imageWidth?: number;
  /**
   * 底图高度 default 145
   */
  imageHeight?: number;
  verifyLocal?: (
    sliderCurrentProgress: number,
    randomBubbleXProgress: number,
    maxOffset: number,
    events: PanGestureHandlerEventPayload[],
    touchTime: number | undefined
  ) => boolean;
  verifyRemote?: (
    isVerifyLocalSuccess: boolean,
    slideValue: number,
    maxOffset: number,
    events: PanGestureHandlerEventPayload[],
    touchTime: number | undefined
  ) => Promise<boolean>;
  /**
   * sliding start
   */
  onSlidingStart?: () => void;
  onSlidingTouchBegin?: () => void;
  /**
   * on refresh clicked
   */
  onRefreshClicked?: () => void;
  onSlidingComplete?: () => void;
  /**
   * replace the all background image sources
   */
  imageBackgroundSources?: ImageSourcePropType[];
  /**
   * replace the all bubble image sources
   */
  imageBubbleSources?: BubbleSource[];
  /**
   * replace the refresh image sources
   */
  imageRefreshSource?: ImageSourcePropType;
  /**
   * 向右滑动滑块解锁拼图
   */
  defaultSliderText?: string;
}

export default function Captcha({
  disableSlider,
  localBubbleBehindType = 'showAfterTouchBegin',
  maxOffset = 3,
  thumbSize = 38,
  bubbleSize = 49,
  imageWidth = 292,
  imageHeight = 145,
  verifyLocal,
  verifyRemote,
  onSlidingStart,
  onSlidingTouchBegin,
  onSlidingComplete,
  imageBackgroundSources,
  imageBubbleSources,
  onRefreshClicked,
  imageRefreshSource,
  defaultSliderText = '向右滑动滑块解锁拼图',
}: CaptchaProps) {
  const sliderMinProgress = useSharedValue(0);
  const sliderMaxProgress = useSharedValue(100);
  const sliderCurrentProgress = useSharedValue(0);
  const maxXProgress = 100;
  const imageContainerBubbleCanMoveMaxWidth = imageWidth - bubbleSize;
  const minXProgress = (bubbleSize / 3 / imageContainerBubbleCanMoveMaxWidth) * 100; // bubble 三分之一

  const getRandomXProgress = useCallback(() => getRandomValueBetween(minXProgress, maxXProgress), [minXProgress]);
  const getRandomYValue = useCallback(
    () => getRandomValueBetween(0, imageHeight - bubbleSize),
    [bubbleSize, imageHeight]
  );
  const sliderAllStatus = useRef<SliderAllStatus>({
    default: {
      backgroundColor: '#f0f0f0',
      borderColor: '#ffffff',
      sliderText: defaultSliderText,
      thumbBackgroundColor: '#ffffff',
      thumbImageSource: require('../../assets/icon-arrow-right-black.png'),
    },
    moving: {
      backgroundColor: 'rgba(47, 108, 224, 0.1)',
      borderColor: '#2f6ce0',
      sliderText: undefined,
      thumbBackgroundColor: '#2f6ce0',
      thumbImageSource: require('../../assets/icon-arrow-right-white.png'),
    },
    success: {
      backgroundColor: 'rgba(59, 206, 118, 0.1)',
      borderColor: '#3bce76',
      sliderText: undefined,
      thumbBackgroundColor: '#3bce76',
      thumbImageSource: require('../../assets/icon-success-white.png'),
    },
    failure: {
      backgroundColor: 'rgba(242, 68, 82, 0.1)',
      borderColor: '#f24452',
      sliderText: undefined,
      thumbBackgroundColor: '#f24452',
      thumbImageSource: require('../../assets/icon-failure-white.png'),
    },
  });
  const [currentSliderItemStatus, setCurrentSliderItemStatus] = useState<SliderItemStatus>(
    sliderAllStatus.current.default
  );

  const innerImageBackgroundSources = useRef<ImageSourcePropType[]>(
    imageBackgroundSources?.length
      ? imageBackgroundSources
      : new Array(16).fill(0).map((_, index) => {
          return {
            uri:
              'https://ess-bucket01-prod.s3.dualstack.cn-northwest-1.amazonaws.com.cn/captcha-image/image-' +
              index +
              '.png',
          };
        })
  );
  const innerImageBubbleSources = useRef<BubbleSource[]>(
    imageBubbleSources?.length
      ? imageBubbleSources
      : [
          {
            behind: require('../../assets/icon-bubble-behind-0.png'),
            above: require('../../assets/icon-bubble-above-0.png'),
          },
          {
            behind: require('../../assets/icon-bubble-behind-1.png'),
            above: require('../../assets/icon-bubble-above-1.png'),
          },
          {
            behind: require('../../assets/icon-bubble-behind-2.png'),
            above: require('../../assets/icon-bubble-above-2.png'),
          },
          {
            behind: require('../../assets/icon-bubble-behind-3.png'),
            above: require('../../assets/icon-bubble-above-3.png'),
          },
        ]
  );
  const innerImageRefreshSource = useRef<ImageSourcePropType>(
    imageRefreshSource ?? require('../../assets/icon-refresh.png')
  );
  const [imageBackgroundSource, setImageBackgroundSource] = useState<ImageSourcePropType>(
    innerImageBackgroundSources.current[0] as ImageSourcePropType
  );
  const [imageBubbleBehindSource, setImageBubbleBehindSource] = useState<ImageSourcePropType>(
    innerImageBubbleSources.current[0]?.behind as ImageSourcePropType
  );
  const [imageBubbleAboveSource, setImageBubbleAboveSource] = useState<ImageSourcePropType>(
    innerImageBubbleSources.current[0]?.above as ImageSourcePropType
  );
  const [localBubbleBehindOpacity, setLocalBubbleBehindOpacity] = useState<number>(
    localBubbleBehindType === 'never' || localBubbleBehindType === 'showAfterTouchBegin' ? 0 : 1
  );
  const [randomXProgress, setRandomXProgress] = useState<number>(getRandomXProgress());
  const [randomYValue, setRandomYValue] = useState(getRandomYValue());
  const [bubbleImageBehindBottom, setBubbleImageBehindBottom] = useState(randomYValue);
  const [bubbleTranslateY, setBubbleTranslateY] = useState(-bubbleImageBehindBottom - 12 - bubbleSize);

  const justRandomX = useCallback(() => {
    const newRandomXValue = getRandomXProgress();
    setRandomXProgress(newRandomXValue);
  }, [getRandomXProgress]);

  const justRandomXY = useCallback(() => {
    justRandomX();
    if (localBubbleBehindType === 'showAfterTouchBegin') {
      setLocalBubbleBehindOpacity(0);
    }
    const newRandomYValue = getRandomYValue();
    setRandomYValue(newRandomYValue);
    const newBubbleImageBehindBottom = newRandomYValue;
    setBubbleImageBehindBottom(newBubbleImageBehindBottom);
    setBubbleTranslateY(-newBubbleImageBehindBottom - 12 - bubbleSize);
  }, [justRandomX, localBubbleBehindType, getRandomYValue, bubbleSize]);

  const onInnerRefreshClicked = useCallback(() => {
    justRandomXY();
    if (innerImageBackgroundSources.current?.length) {
      const randomIndex = Math.floor(getRandomValueBetween(0, innerImageBackgroundSources.current.length - 1));
      const newSource = innerImageBackgroundSources.current[randomIndex];
      if (newSource) {
        setImageBackgroundSource(newSource);
      }
    }
    if (innerImageBubbleSources.current?.length) {
      const randomIndex = Math.floor(getRandomValueBetween(0, innerImageBubbleSources.current.length - 1));
      const newSource = innerImageBubbleSources.current[randomIndex];
      if (newSource) {
        setImageBubbleBehindSource(newSource.behind);
        setImageBubbleAboveSource(newSource.above);
      }
    }

    sliderCurrentProgress.value = 0;
    setCurrentSliderItemStatus(sliderAllStatus.current.default);
    onRefreshClicked?.();
  }, [justRandomXY, onRefreshClicked, sliderCurrentProgress]);

  const innerVerifyLocal = (
    sliderValue: number,
    events: PanGestureHandlerEventPayload[],
    touchTime: number | undefined
  ): boolean => {
    if (verifyLocal) {
      return verifyLocal(sliderValue, randomXProgress, maxOffset, events, touchTime);
    }
    const yAxisArray = events.map((item) => item.y);
    const yAxisAverage = yAxisArray.reduce(sum) / yAxisArray.length;
    const yAxisDeviations = yAxisArray.map((x) => x - yAxisAverage);
    const yAxisStandardDeviation = Math.sqrt(yAxisDeviations.map(square).reduce(sum) / yAxisArray.length);
    const verifiedYAxisStandardDeviation = yAxisStandardDeviation !== 0;
    return (
      verifiedYAxisStandardDeviation &&
      (touchTime ?? 0) >= 300 &&
      (randomXProgress ?? 0) >= sliderCurrentProgress.value - maxOffset &&
      (randomXProgress ?? 0) <= sliderCurrentProgress.value + maxOffset
    );
  };

  const onInnerSlidingTouchBegin = () => {
    onSlidingTouchBegin?.();
  };
  const onInnerSlidingStart = () => {
    // setCurrentSliderItemStatus(sliderAllStatus.current.moving);
    if (localBubbleBehindType === 'showAfterTouchBegin') {
      justRandomX();
      setLocalBubbleBehindOpacity(1);
    }
    setCurrentSliderItemStatus(sliderAllStatus.current.moving);
    onSlidingStart?.();
  };
  const handleVerify = async (
    slideValue: number,
    events: PanGestureHandlerEventPayload[],
    touchTime: number | undefined
  ) => {
    if (typeof touchTime === 'undefined') {
      // onInnerRefreshClicked();
      return;
    }
    const isVerifyLocalSuccess = innerVerifyLocal(slideValue, events, touchTime);
    let isVerifyFinalSuccess = isVerifyLocalSuccess;
    if (verifyRemote) {
      try {
        // noinspection UnnecessaryLocalVariableJS
        const isVerifyRemoteSuccess = await verifyRemote?.(
          isVerifyLocalSuccess,
          slideValue,
          maxOffset,
          events,
          touchTime
        );
        isVerifyFinalSuccess = isVerifyRemoteSuccess;
      } catch (e) {
        console.log('> call onSlidingComplete failure', e);
      }
    }

    if (isVerifyFinalSuccess) {
      setCurrentSliderItemStatus(sliderAllStatus.current.success);
    } else {
      setCurrentSliderItemStatus(sliderAllStatus.current.failure);
    }
    // sliderCurrentProgress.value = 0;
  };
  const onInnerSlidingComplete = (
    slideValue: number,
    events: PanGestureHandlerEventPayload[],
    touchTime: number | undefined
  ) => {
    handleVerify(slideValue, events, touchTime).then();
    onSlidingComplete?.();
  };

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

  return (
    <GestureHandlerRootView style={styles.captchaRoot}>
      <View style={[styles.captchaContainer, { height: imageHeight + 90 }]}>
        <View
          style={[
            styles.captchaImageContainer,
            {
              width: imageWidth,
              height: imageHeight,
            },
          ]}
        >
          <Image source={imageBackgroundSource} style={styles.captchaImage} />
          {(localBubbleBehindType === 'showAfterTouchBegin' || localBubbleBehindType === 'always') && (
            <Image
              source={imageBubbleBehindSource}
              style={[
                styles.bubbleImageBehind,
                {
                  opacity: localBubbleBehindOpacity,
                  left: imageContainerBubbleCanMoveMaxWidth * ((randomXProgress ?? 0) / 100.0),
                  width: bubbleSize,
                  height: bubbleSize,
                  bottom: bubbleImageBehindBottom,
                },
              ]}
            />
          )}
          <TouchableOpacity
            style={styles.bubbleImageRefreshContainer}
            onPress={() => {
              onInnerRefreshClicked();
            }}
          >
            <Image source={innerImageRefreshSource.current} style={[styles.bubbleImageRefreshImage]} />
          </TouchableOpacity>
        </View>
        <Slider
          disableTapEvent={true}
          disable={disableSlider ?? false}
          style={[styles.captchaSliderRoot, { height: thumbSize, width: imageWidth }]}
          containerStyle={[styles.captchaSliderContainer, { height: thumbSize, width: imageWidth }]}
          bubbleContainerStyle={[styles.bubbleContainerStyle, { height: bubbleSize }]}
          progress={sliderCurrentProgress}
          minimumValue={sliderMinProgress}
          maximumValue={sliderMaxProgress}
          bubbleWidth={bubbleSize}
          thumbWidth={thumbSize}
          sliderText={currentSliderItemStatus.sliderText}
          sliderHeight={thumbSize}
          bubbleTranslateY={bubbleTranslateY}
          bubbleAlwaysShow={true}
          bubbleUseThumbTranslateX={true}
          onSlidingComplete={onInnerSlidingComplete}
          onSlidingStart={onInnerSlidingStart}
          onSlidingTouchBegin={onInnerSlidingTouchBegin}
          minimumTrackBorderWidth={1}
          minimumTrackBorderRadius={2}
          minimumTrackContainerOutside={true}
          theme={{
            minimumTrackTintColor: currentSliderItemStatus.backgroundColor,
            minimumTrackBorderColor: currentSliderItemStatus.borderColor,
          }}
          renderThumb={() => (
            <View
              style={[
                styles.thumb,
                { width: thumbSize, height: thumbSize, backgroundColor: currentSliderItemStatus.thumbBackgroundColor },
              ]}
            >
              <Image source={currentSliderItemStatus.thumbImageSource} style={styles.thumbImage} />
            </View>
          )}
          renderBubble={() => (
            <View style={[styles.bubbleContainerStyle, { height: bubbleSize }]}>
              <Image
                source={imageBubbleAboveSource}
                style={[styles.bubbleImageAbove, { width: bubbleSize, height: bubbleSize }]}
              />
            </View>
          )}
        />
      </View>
    </GestureHandlerRootView>
  );
}
const styles = StyleSheet.create({
  captchaRoot: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    zIndex: 99,
    width: '100%',
    height: '100%',
    backgroundColor: 'rgba(51, 51, 51, 0.5)',
    flex: 1,
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
  },
  captchaContainer: {
    borderRadius: 12,
    backgroundColor: '#fff',
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'flex-start',
    alignItems: 'flex-start',
    paddingVertical: 20,
    paddingHorizontal: 14,
  },
  captchaImageContainer: {
    position: 'relative',
  },
  captchaImage: {
    width: '100%',
    height: '100%',
    borderTopLeftRadius: 6,
    borderTopRightRadius: 6,
  },
  captchaSliderRoot: {
    marginTop: 12,
    padding: 0,
    margin: 0,
    justifyContent: 'flex-start',
    alignItems: 'flex-start',
  },
  captchaSliderContainer: {
    padding: 0,
    margin: 0,
    borderRadius: 2,
    backgroundColor: '#f0f0f0',
    borderStyle: 'solid',
    borderColor: 'rgba(51, 51, 51, 0.1)',
    borderWidth: 1,
  },
  thumb: {
    padding: 0,
    margin: 0,
    borderRadius: 2,
    shadowColor: 'rgba(0, 0, 0, 0.12)',
    shadowOffset: {
      width: 0,
      height: 0,
    },
    shadowRadius: 4,
    elevation: 4,
    shadowOpacity: 1,
    overflow: 'hidden',
    alignItems: 'center',
    justifyContent: 'center',
  },
  bubbleContainerStyle: {
    backgroundColor: 'transparent',
    margin: 0,
    alignItems: 'center',
  },
  bubbleImageAbove: {
    padding: 0,
    margin: 0,
  },
  thumbImage: {
    width: 24,
    height: 24,
  },
  bubbleImageBehind: {
    position: 'absolute',
  },
  bubbleImageRefreshContainer: {
    position: 'absolute',
    right: 0,
    top: 0,
    zIndex: 9,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 7,
  },
  bubbleImageRefreshImage: {
    width: 16,
    height: 16,
  },
});
