import { View, Platform } from 'react-native';
import React, { memo, useCallback, useEffect, useRef } from 'react';
import { Camera, useCameraFormat } from 'react-native-vision-camera';
import RNPhotoManipulator, { MimeType } from 'react-native-photo-manipulator';
import Logger from '../../utils/logger';
import {
  LivenessUserAction,
  type LivenessScanHandle,
  LivenessErrorCode,
  type InitialiseLivenessScanProps,
  LivenessError,
} from '../../types/sdkTypes';

import { type GetIdSdk } from '../../index';
import * as GetidSdkTypes from '../../types/sdkTypes';
import { useCameraSetup } from '../../hooks';
import CameraStyles from '../../styles/CameraStyles';
import { FaceOverlay } from '../Overlay/FaceOverlay';
import {
  rectangleHeight,
  rectangleWidth,
  windowHeight,
  windowWidth,
} from '../Overlay/faceConstants';
import { useLiveness, type LivenessCommand } from './useLiveness';
import { GetIDProvider } from '../../contexts/getid-context';

type LivenessScanProps = {
  sdk: GetIdSdk;
} & InitialiseLivenessScanProps;

const LivenessScan = React.forwardRef((props: LivenessScanProps, ref) => {
  useEffect(() => {
    Logger.log('LivenessScan component mounted');
  }, []);

  const [captureStarted, setCaptureStarted] = React.useState(false);
  const [overlayStatus, setOverlayStatus] = React.useState('');
  const [cameraStarted, setCameraStarted] = React.useState(false);
  const [appHeight, setAppHeight] = React.useState(windowHeight);
  const [isReady, setIsReady] = React.useState(false);
  const [isInitCalled, setIsInitCalled] = React.useState(false);
  const [postponedInit, setPostponedInit] = React.useState(false);

  const { cameraRef, hasPermission, device } = useCameraSetup({
    type: 'front',
  });

  const capturePromise = useRef({
    resolve: (_data: any) => {},
    reject: (_data: any) => {},
  });

  const takePhoto = useCallback(async () => {
    if (!cameraRef.current) {
      return null;
    }
    const photo = await cameraRef.current.takeSnapshot({
      quality: 50,
    });

    const imagePath = `file://${photo.path}`;

    try {
      const MAX_WIDTH = 140;
      const aspectRatio = photo.width / photo.height;
      const MAX_HEIGHT = MAX_WIDTH / aspectRatio;

      const targetSize = {
        width: Math.min(photo.width, MAX_WIDTH),
        height: Math.min(photo.height, MAX_HEIGHT),
      };

      const cropRegion = {
        x: 0,
        y: 0,
        width: photo.width,
        height: photo.height,
      };

      const resized = await RNPhotoManipulator.crop(
        imagePath,
        cropRegion,
        targetSize,
        MimeType.JPEG
      );

      try {
        const result = await fetch(resized);
        const data = await result.blob();

        return data;
      } catch (e) {
        console.error('Error:', e);
      }
    } catch (e) {
      console.error('Error:', e);
    }
    return null;
  }, [cameraRef]);

  const mapToCode = <T extends string>(value?: string): T => {
    if (!value) return value as T;

    return value.replace(/([a-z0–9])([A-Z])/g, '$1-$2').toLowerCase() as T;
  };

  const onUserHintChangeLocal = useCallback(
    (data: LivenessCommand) => {
      switch (data.messageType) {
        case 'taskComplete':
          setOverlayStatus('success');
          props.onUserHintChange?.({
            type: 'action',
            value: LivenessUserAction.ACTION_COMPLETED,
          });
          break;
        case 'task':
          setOverlayStatus('neutral');
          const actionCode = mapToCode<LivenessUserAction>(data.task);
          props.onUserHintChange?.({
            type: 'action',
            value: actionCode,
          });
          break;
        case 'warning':
          setOverlayStatus('neutral');
          const warningCode = mapToCode<LivenessErrorCode>(data.warningType);
          capturePromise.current.reject(
            new LivenessError(data.messageType, warningCode)
          );
          break;
        case 'failure':
          setOverlayStatus('neutral');
          const failureCode = mapToCode<LivenessErrorCode>(data.errorType);
          capturePromise.current.reject(
            new LivenessError(data.messageType, failureCode)
          );
          break;
        case 'success':
          props.sdk.tracking.trySendEvent('liveness', 'completed');
          setOverlayStatus('success');
          props.sdk.addLivenessResult(data);
          capturePromise.current.resolve(undefined);
          break;
      }
    },
    [props, capturePromise]
  );

  const onError = useCallback(
    (_: any) => {
      capturePromise.current.reject(
        new LivenessError(
          'Internal error occurred',
          GetidSdkTypes.LivenessErrorCode.SERVER_UNAVAILABLE
        )
      );
    },
    [capturePromise]
  );

  const { init, stop } = useLiveness({
    sdk: props.sdk,
    onUserHintChange: onUserHintChangeLocal,
    onServerValidationStarted: props.onServerValidationStarted,
    takePhoto: takePhoto,
    onError,
  });

  useEffect(() => {
    setTimeout(() => {
      setCameraStarted(true);
    }, 50);
  }, []);

  useEffect(() => {
    if (isReady && captureStarted && !isInitCalled && postponedInit) {
      setPostponedInit(false);
      try {
        setIsInitCalled(true);
        init();
      } catch (e) {
        capturePromise.current.reject(e);
      }
    }
  }, [isReady, isInitCalled, captureStarted, init, postponedInit]);

  const wDiff = (windowWidth - rectangleWidth) / 2;
  const hDiff = (appHeight - rectangleHeight) / 2;
  const rect = {
    centerW: windowWidth / 2,
    centerH: appHeight / 2,
    width: rectangleWidth,
    height: rectangleHeight,
    top: hDiff,
    left: wDiff,
    right: windowWidth - wDiff,
    bottom: appHeight - hDiff,
  };

  const stopCapture = () => {
    Logger.log('Stopping Capture process');
    try {
      setCaptureStarted(false);
      setIsInitCalled(false);
      stop();
    } catch (e) {
      Logger.error(`Error: ${e}`, props.sdk.tracking.trySendEvent);
    }
  };

  const capture = () => {
    return new Promise((resolve, reject) => {
      Logger.log('Starting Capture process');

      capturePromise.current = {
        resolve: (data) => {
          stopCapture();
          return resolve(data);
        },
        reject: (error) => {
          stopCapture();
          return reject(error);
        },
      };
      if (!device) {
        Logger.log('Camera device not found');
        return capturePromise.current.reject(
          new LivenessError(
            'Camera device not found',
            GetidSdkTypes.GeneralErrorCode.NO_CAMERA_DEVICE
          )
        );
      }
      if (!hasPermission) {
        Logger.log('Camera permission not granted');
        return capturePromise.current.reject(
          new LivenessError(
            'Camera permission not granted',
            GetidSdkTypes.GeneralErrorCode.NO_CAMERA_PERMISSION
          )
        );
      }
      setCaptureStarted(true);
      try {
        if (isReady && !isInitCalled) {
          setIsInitCalled(true);
          init();
        } else {
          setPostponedInit(true);
        }
      } catch (e) {
        reject(e);
      }
    });
  };

  React.useImperativeHandle(
    ref,
    () =>
      ({
        capture,
        stopCapture,
      }) as LivenessScanHandle
  );

  const format = useCameraFormat(device, [
    {
      photoResolution: {
        height: 1024,
        width: 768,
      },
      photoHdr: false,
      videoResolution: {
        height: 1024,
        width: 768,
      },
      videoHdr: false,
      videoStabilizationMode: 'off',
      fps: 30,
    },
  ]);

  const setReadyState = () => {
    setTimeout(() => {
      setIsReady(true);
      setTimeout(() => {
        if (props.onCameraReady) {
          props.onCameraReady();
        }
      }, 400);
    }, 50);
  };

  return (
    <GetIDProvider sdk={props.sdk}>
      <View
        style={CameraStyles.cameraWrapper}
        onLayout={(e) => setAppHeight(e.nativeEvent.layout.height)}
      >
        {device && hasPermission && (
          <Camera
            onInitialized={() => setReadyState()}
            ref={cameraRef}
            device={device}
            isActive={true}
            format={format}
            style={
              cameraStarted ? CameraStyles.camera : CameraStyles.cameraDisabled
            }
            photo={true}
            photoHdr={false}
            photoQualityBalance="speed"
            video={Platform.OS === 'ios'}
            videoHdr={false}
            enableBufferCompression={true}
            videoStabilizationMode={'off'}
            exposure={0}
          />
        )}
        {isReady && <FaceOverlay status={overlayStatus} rect={rect} />}
      </View>
    </GetIDProvider>
  );
});

export default memo(LivenessScan);
