import React, {useCallback, useEffect, useRef, useState} from 'react';
import {StyleSheet, View} from 'react-native';
import {useTranslation} from 'react-i18next';
import {
  useCameraDevice,
  useCameraPermission,
  Camera as CameraVC,
  useFrameProcessor,
  PhotoFile,
} from 'react-native-vision-camera';
import {Face, useFaceDetector} from 'react-native-vision-camera-face-detector';
import {
  useSharedValue as useSharedValueWorklets,
  Worklets,
} from 'react-native-worklets-core';
import {
  useAnimatedProps,
  withTiming,
  useSharedValue as useSharedValueReanimated,
  cancelAnimation,
  runOnJS,
} from 'react-native-reanimated';
import {useMutation} from 'react-query';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {IconArrowLeft} from '@tabler/icons-react-native';
import ImageEditor from '@react-native-community/image-editor';
import {useNetInfo} from '@react-native-community/netinfo';
import {ApiErrorResponse} from 'apisauce';
import {
  Alert,
  Button,
  CloseButton,
  IconButton,
  Image,
  Spinner,
  Typography,
} from '@components';
import {HumandAPIError} from '@config/api';
import {useModalHandler} from '@hooks/useModalHandler';
import {Navigation} from '@interfaces/navigation';
import {openTimeTracker} from '@modules/timeTracking/redux';
import {
  compareFaceRecognitionImage,
  setUpFaceRecognitionImage,
} from '@modules/timeTracking/services';
import {useTimeTracker, useTimeoutHandler} from '@modules/timeTracking/hooks';
import FaceDetectionConfirmation from '@modules/timeTracking/module';
import NoConnectionDialog from '@modules/timeTracking/components/NoConnectionDialog';
import {useAppSelector} from '@redux/utils';
import {AMPLITUDE_EVENTS, Screens, windowDimensions} from '@shared/constants';
import {SPACING, useTheme} from '@shared/theme';
import {logAmplitudeEvent, sentryLogs} from '@shared/utils';

import {styles} from './styles';
import {
  CIRCLE_X_POSITION,
  CIRCLE_Y_POSITION,
  CIRCUMFERENCE,
  FACE_BOUNDS as FACE_BOUNDS_SET_UP,
  LOGIC_BOUNDS_FOR_COMPARISON,
  FACE_DETECTION_OPTIONS,
  RADIUS,
  CROPPED_SIZE,
} from './constants';
import CircleLayer from './components/CircleLayer';

enum TakePhotoStatus {
  Waiting = 'WAITING',
  Rejected = 'REJECTED',
  NotRecognized = 'NOT_RECOGNIZED',
  FailedCropped = 'FAILED_CROPPED',
}

function Camera({
  navigation,
  route: {
    params: {isSetUp = false},
  },
}: Navigation<Screens.CAMERA>) {
  const {theme} = useTheme();
  const {bottom, top} = useSafeAreaInsets();
  const {t} = useTranslation();
  const {isInternetReachable} = useNetInfo();
  const {getAllRegistersAndExtraData} = useTimeTracker();
  const {isVisible, onCloseModal, onOpenModal} = useModalHandler();
  const timeTrackingPolicies = useAppSelector(
    ({timeTracking}) => timeTracking.policies,
  );
  const progressCircle = useSharedValueReanimated(CIRCUMFERENCE);
  const {detectFaces} = useFaceDetector(FACE_DETECTION_OPTIONS);
  const {hasPermission} = useCameraPermission();
  const [photo, setPhoto] = useState<Nullable<PhotoFile>>(null);
  const [state, setState] = useState<TakePhotoStatus>(TakePhotoStatus.Waiting);
  const inactivityCounterId = useRef<NodeJS.Timeout>(undefined);
  const preventOpenModal = useRef(false);
  const croppedPhotoUri = useRef<Nullable<string>>(null);
  const stabilizerTimeout = useRef<NodeJS.Timeout>(undefined);
  const cameraRef = useRef<CameraVC>(null);
  const hasDetectedFaceInBounds = useSharedValueWorklets(false);
  const device = useCameraDevice('front');
  const wasNotRecognized = state === TakePhotoStatus.NotRecognized;
  const wasRejected = state === TakePhotoStatus.Rejected;
  const isFailedCropped = state === TakePhotoStatus.FailedCropped;
  const hasErrorStatus = wasNotRecognized || wasRejected || isFailedCropped;
  const FACE_BOUNDS = isSetUp
    ? FACE_BOUNDS_SET_UP
    : LOGIC_BOUNDS_FOR_COMPARISON;
  const canContinueRejected =
    wasRejected && !timeTrackingPolicies?.faceRecognitionBlocking;

  const {mutate: uploadSetUpImage, isLoading: setUpLoading} = useMutation({
    mutationFn: setUpFaceRecognitionImage,
    onSuccess: () => {
      getAllRegistersAndExtraData();
      navigation.navigate(Screens.LOCATIONS, {comingFromCamera: true});
    },
    onError: (error: ApiErrorResponse<HumandAPIError>) => {
      if (error.status && error.status >= 500) {
        logAmplitudeEvent(AMPLITUDE_EVENTS.TIME_TRACKING_SET_UP_IMAGE_ERROR, {
          status: error.status,
          code: error.data?.code || 'UNKNOWN_CODE',
        });
        navigation.navigate(Screens.SERVICE_IN_MAINTENANCE, {popQuantity: 2});
      }
    },
  });

  const {mutate: compareImage, isLoading: compareImageLoading} = useMutation({
    mutationFn: compareFaceRecognitionImage,
    onSuccess: data => {
      if (data.passed) {
        navigation.navigate(Screens.LOCATIONS, {comingFromCamera: true});
      } else {
        setState(TakePhotoStatus.Rejected);
      }
    },
    onError: () => {
      setState(TakePhotoStatus.Rejected);
    },
  });

  const animatedProps = useAnimatedProps(() => {
    return {strokeDashoffset: progressCircle.value};
  }, []);

  useTimeoutHandler();

  const onRepeatPhoto = useCallback(() => {
    setState(TakePhotoStatus.Waiting);
    if (isSetUp) {
      setPhoto(null);
      hasDetectedFaceInBounds.value = false;
      progressCircle.value = CIRCUMFERENCE;
      croppedPhotoUri.current = null;
    } else {
      if (inactivityCounterId.current) {
        clearTimeout(inactivityCounterId.current);
        inactivityCounterId.current = undefined;
      }
      setPhoto(null);
      hasDetectedFaceInBounds.value = false;
      croppedPhotoUri.current = null;
    }
  }, [hasDetectedFaceInBounds, isSetUp, progressCircle]);

  useEffect(() => {
    const blur = navigation.addListener('blur', () => {
      preventOpenModal.current = true;
      if (inactivityCounterId.current) {
        clearTimeout(inactivityCounterId.current);
        inactivityCounterId.current = undefined;
      }
      if (stabilizerTimeout.current) {
        clearTimeout(stabilizerTimeout.current);
        stabilizerTimeout.current = undefined;
      }
    });
    const focus = navigation.addListener('focus', () => {
      preventOpenModal.current = false;
      onRepeatPhoto();
    });
    const beforeRemove = navigation.addListener('beforeRemove', () => {
      if (!preventOpenModal.current && !isSetUp) {
        openTimeTracker();
      }
    });
    return () => {
      blur();
      focus();
      beforeRemove();
    };
  }, [isSetUp, navigation, onRepeatPhoto]);

  const onTakePhoto = async () => {
    const {x, y, height, width} = FACE_BOUNDS;
    const takenPhoto = await cameraRef?.current?.takeSnapshot({quality: 100});
    if (takenPhoto) {
      const scaleFactorY = takenPhoto.height / windowDimensions.height;
      const scaleFactorX = takenPhoto.width / windowDimensions.width;
      const croppedPhotoResult = await ImageEditor.cropImage(
        `file://${takenPhoto.path}`,
        {
          offset: {x: x * scaleFactorX, y: y * scaleFactorY},
          size: {width: width * scaleFactorX, height: height * scaleFactorY},
          displaySize: {width: CROPPED_SIZE, height: CROPPED_SIZE},
        },
      );
      croppedPhotoUri.current = croppedPhotoResult.uri;
      const croppedPhotoHasFace = await FaceDetectionConfirmation.hasFace(
        croppedPhotoResult.uri,
      );
      if (croppedPhotoHasFace) {
        setPhoto(takenPhoto);
      } else {
        croppedPhotoUri.current = null;
        setState(
          isSetUp
            ? TakePhotoStatus.FailedCropped
            : TakePhotoStatus.NotRecognized,
        );
        sentryLogs.warn({
          component: 'TT Camera',
          message: 'Cropped photo does not have a face in the bounds',
          module: 'TIME_TRACKING',
        });
      }
    }
  };

  const onComparePhoto = () => {
    if (!isInternetReachable) {
      onOpenModal();
    } else if (croppedPhotoUri.current) {
      compareImage({image: croppedPhotoUri.current});
    }
  };

  const onInBounds = () => {
    if (isSetUp) {
      if (!isFailedCropped) {
        progressCircle.value = withTiming(0, {duration: 2000}, finished => {
          finished && runOnJS(onTakePhoto)();
        });
      }
    } else {
      if (!wasNotRecognized) {
        if (inactivityCounterId.current) {
          clearTimeout(inactivityCounterId.current);
          inactivityCounterId.current = undefined;
        }
        stabilizerTimeout.current = setTimeout(async () => {
          await onTakePhoto();
          onComparePhoto();
        }, 1000);
      }
    }
  };

  const startInactivityCounter = () => {
    if (!inactivityCounterId.current) {
      inactivityCounterId.current = setTimeout(() => {
        setState(TakePhotoStatus.NotRecognized);
      }, 7000);
    }
  };

  const onBoundsLost = () => {
    if (isSetUp) {
      cancelAnimation(progressCircle);
      progressCircle.value = CIRCUMFERENCE;
    } else {
      if (stabilizerTimeout.current) {
        clearTimeout(stabilizerTimeout.current);
        stabilizerTimeout.current = undefined;
      }
    }
  };

  const onOutOfBounds = () => {
    !isSetUp && startInactivityCounter();
  };

  const onFacesDetection = Worklets.createRunOnJS((faces: Face[]) => {
    if (Object.keys(faces).length <= 0) {
      onOutOfBounds();
      if (hasDetectedFaceInBounds.value) {
        hasDetectedFaceInBounds.value = false;
        onBoundsLost();
      }
      return;
    }
    const {bounds} = faces[0];
    const {width, height, x, y} = bounds;
    if (
      y >= FACE_BOUNDS.y &&
      x >= FACE_BOUNDS.x &&
      width + x <= FACE_BOUNDS.x + FACE_BOUNDS.width &&
      height + y <= FACE_BOUNDS.y + FACE_BOUNDS.height
    ) {
      if (!hasDetectedFaceInBounds.value) {
        hasDetectedFaceInBounds.value = true;
        onInBounds();
      }
    } else {
      if (hasDetectedFaceInBounds.value) {
        hasDetectedFaceInBounds.value = false;
        onBoundsLost();
      }
      onOutOfBounds();
    }
  });

  const frameProcessor = useFrameProcessor(
    frame => {
      'worklet';
      const faces = detectFaces(frame);
      onFacesDetection(faces);
    },
    [onFacesDetection],
  );

  const onContinueRejected = () => {
    if (isInternetReachable) {
      navigation.navigate(Screens.LOCATIONS, {
        faceRejected: true,
        comingFromCamera: true,
      });
    } else {
      onOpenModal();
    }
  };

  const onSetUpConfirmed = () => {
    if (!isInternetReachable) {
      onOpenModal();
    } else if (croppedPhotoUri.current) {
      uploadSetUpImage({image: croppedPhotoUri.current});
    }
  };

  return device ? (
    <View style={styles.container}>
      {photo ? (
        <Image
          source={{uri: `file://${photo.path}`}}
          style={styles.imageContainer}
        />
      ) : hasPermission ? (
        <CameraVC
          photo
          isActive
          ref={cameraRef}
          exposure={1}
          outputOrientation="preview"
          pixelFormat="yuv"
          style={StyleSheet.absoluteFill}
          device={device}
          frameProcessor={frameProcessor}
        />
      ) : null}
      <CircleLayer
        animatedProps={animatedProps}
        radius={RADIUS}
        circumferenceLength={CIRCUMFERENCE}
        xPosition={CIRCLE_X_POSITION}
        yPosition={CIRCLE_Y_POSITION}
        isSetUp={isSetUp}
        hideCircle={hasErrorStatus}
      />
      {canContinueRejected && (
        <View style={[styles.headerContainer, {top: top + SPACING.x2}]}>
          <IconButton
            Icon={IconArrowLeft}
            iconColor={theme.text.neutral.inverted}
            variant="flat"
          />
          <Typography
            color={theme.text.neutral.inverted}
            variant="m"
            weight="semiBold">
            {t('general.back')}
          </Typography>
        </View>
      )}
      {compareImageLoading ? (
        <View style={styles.belowCircleContainer}>
          <Spinner dark />
        </View>
      ) : !photo && !hasErrorStatus ? (
        <Typography
          align="center"
          color={theme.text.neutral.inverted}
          style={styles.belowCircleContainer}>
          {t(
            `time_tracker.${
              isSetUp
                ? hasDetectedFaceInBounds.value
                  ? 'face_set_up_in_bounds'
                  : 'face_set_up_title'
                : 'face_check'
            }`,
          )}
        </Typography>
      ) : null}
      {(photo && isSetUp) || hasErrorStatus ? (
        <>
          {!hasErrorStatus && (
            <View style={styles.belowCircleContainer}>
              <Typography
                color={theme.text.neutral.inverted}
                variant="l"
                weight="semiBold">
                {t('time_tracker.ready')}
              </Typography>
              <Typography
                align="center"
                color={theme.text.neutral.inverted}
                style={styles.spaceContainer}>
                {t('time_tracker.face_set_up_ready')}
              </Typography>
            </View>
          )}
          {(isSetUp || hasErrorStatus) && !compareImageLoading && (
            <View style={[styles.bottomContainer, {paddingBottom: bottom}]}>
              {hasErrorStatus && (
                <Alert
                  description={t(
                    `time_tracker.${
                      canContinueRejected
                        ? 'can_continue'
                        : 'not_recognized_help'
                    }`,
                  )}
                  style={styles.warningContainer}
                  title={t('time_tracker.not_recognized')}
                  variant="warning"
                />
              )}
              <Button
                text={t(
                  `time_tracker.${
                    isSetUp && !isFailedCropped ? 'confirm' : 'try_again'
                  }`,
                )}
                isLoading={setUpLoading}
                onPress={
                  isSetUp && !isFailedCropped ? onSetUpConfirmed : onRepeatPhoto
                }
              />
              <Button
                text={t(
                  isSetUp
                    ? isFailedCropped
                      ? 'general.exit'
                      : 'time_tracker.repeat_photo'
                    : canContinueRejected
                    ? 'time_tracker.continue_rejected'
                    : 'general.exit',
                )}
                variant="secondary"
                disabled={setUpLoading}
                onPress={
                  isSetUp
                    ? isFailedCropped
                      ? navigation.goBack
                      : onRepeatPhoto
                    : canContinueRejected
                    ? onContinueRejected
                    : navigation.goBack
                }
                style={styles.spaceContainer}
              />
            </View>
          )}
        </>
      ) : !compareImageLoading ? (
        <View style={styles.closeButton}>
          <CloseButton
            iconColor={theme.text.neutral.brand}
            onPress={navigation.goBack}
            style={styles.internalButton}
            variant="secondary"
          />
        </View>
      ) : null}
      <NoConnectionDialog isVisible={isVisible} onClose={onCloseModal} />
    </View>
  ) : null;
}

export default Camera;
