import React, {
  createContext,
  PropsWithChildren,
  useCallback,
  useContext,
  useEffect,
  useRef,
  useState,
} from 'react';
import {AppState, AppStateStatus} from 'react-native';
import {useQueryClient} from 'react-query';
import {trackCustomHumandError} from '@hooks/useGeneralError';
import {
  CallingState,
  CALLS_QUERY_KEYS,
  routeOptionsEmpty,
  supportedDevices,
} from '@modules/calls/constants';
import {initCall} from '@modules/calls/services';
import {
  AcceptCallErrorCodes,
  AudioRouteAllOptions,
  Call,
  CallStatus,
} from '@modules/calls/interfaces';
import {
  callStatusUpdater,
  checkBluetoothPermission,
  CallService,
} from '@modules/calls/utils';
import {
  setCallId,
  setCallParticipants,
  setCallingState,
  setGroupCall,
  setMediaState,
} from '@modules/calls/store';
import {
  useCallingState,
  useCameraEnabled,
  useGroupCall,
  useMicrophoneEnabled,
  useSpeakerEnabled,
} from '@modules/calls/store/selectors';
import TrackPlayer from '@modules/chats/instances/TrackPlayer';
import {useCallPermissions} from '@modules/calls/hooks/useCallPermissions';
import {useAppSelector} from '@redux/utils';
import {HUMAND_URL} from '@shared/keys';
import {isAndroid, isIos} from '@shared/utils';
import {useFeatureFlag} from '@stores/featureFlags';

import {CallConnectionFeedback} from '../CallConnectionFeedback';
import {CallParticipantsLimit} from '../CallParticipantsLimit';
import {DialogMissingCallPermission} from '../DialogMissingCallPermission';
import {useCallNativeListeners} from './listeners';

const ALL_PARTICIPANTS_LIMIT = 300;

interface Props extends PropsWithChildren {
  call: Call;
  isCaller: boolean;
  joinedFromLink?: boolean;
  initialCameraEnabled?: boolean;
}

type CallProviderContextType = {
  permissionGranted: boolean;
  maxParticipantsAllowed: number;
  callLink: string;
  audioRouteOptions: AudioRouteAllOptions[];
  selectedAudioRouteOption: AudioRouteAllOptions;
  setRouteOptions: (option: AudioRouteAllOptions[]) => void;
  setSelectedAudioRouteOption: (option: AudioRouteAllOptions) => void;
  setPermissionGranted: (granted: boolean) => void;
};

const CallProviderContext = createContext({} as CallProviderContextType);

export const useCallProviderOptions = () => {
  return useContext(CallProviderContext);
};

export function CallProvider({
  call,
  isCaller,
  joinedFromLink,
  initialCameraEnabled,
  children,
}: Props) {
  const queryClient = useQueryClient();
  const [initializedCallId, setInitializedCallId] = useState<string | null>(
    null,
  );
  const [isError, setIsError] = useState(false);
  const groupCall = useGroupCall();
  const isGroupCall = groupCall?.isGroupCall;
  const [audioRouteOptions, setRouteOptions] = useState(routeOptionsEmpty);
  const [permissionGranted, setPermissionGranted] = useState(false);
  const [showParticipantsLimitUI, setShowParticipantsLimitUI] = useState(false);
  const [showMicPermissionDialog, setShowMicPermissionDialog] = useState(false);
  const [selectedAudioRouteOption, setSelectedAudioRouteOption] =
    useState<AudioRouteAllOptions>(
      call.initializationConfig?.cameraEnabled
        ? AudioRouteAllOptions.SpeakerPhone
        : AudioRouteAllOptions.Earpiece,
    );
  const microphoneEnabled = useMicrophoneEnabled();
  const currentCameraEnabled = useCameraEnabled();
  const speakerEnabled = useSpeakerEnabled();
  const existingCallId = useAppSelector(state => state.call.callId);
  const existingCallingState = useCallingState();
  const [initialAudioRouteSynced, setInitialAudioRouteSynced] = useState(false);

  // Link to be used to copy/share the call
  const callLink = `${HUMAND_URL}call/${call.id}/${call.providerCallId}`;

  const isPipEnabled = useFeatureFlag('CALLS_PICTURE_IN_PICTURE_ENABLED');
  const {
    ensureCameraPermission,
    ensureMicrophonePermission,
    hasMicrophonePermission,
  } = useCallPermissions();

  useEffect(() => {
    if (isAndroid) {
      CallService.setPipEnabled(!!isPipEnabled);
    }
  }, [isPipEnabled]);

  const handleAudioRouteChanged = useCallback(
    ({
      audioRouteOptions: newOptions,
      selectedAudioRoute,
    }: {
      audioRouteOptions: AudioRouteAllOptions[];
      selectedAudioRoute: AudioRouteAllOptions;
    }) => {
      setRouteOptions(newOptions);
      setSelectedAudioRouteOption(selectedAudioRoute);

      const shouldEnableSpeaker =
        selectedAudioRoute === AudioRouteAllOptions.SpeakerPhone;

      if (speakerEnabled !== shouldEnableSpeaker) {
        setMediaState({
          speakerEnabled: shouldEnableSpeaker,
        });
      }
    },
    [speakerEnabled, setRouteOptions, setSelectedAudioRouteOption],
  );

  useCallNativeListeners({
    onAudioRouteChanged: handleAudioRouteChanged,
  });

  useEffect(() => {
    setInitialAudioRouteSynced(false);
  }, [call.id]);

  useEffect(() => {
    if (initialAudioRouteSynced || !call.id) {
      return;
    }

    let isMounted = true;

    const syncInitialMediaState = async () => {
      const [nativeState, hardwareRoute] = await Promise.all([
        CallService.getNativeCallState(),
        (async () => {
          try {
            const route = (await CallService.getCurrentAudioRoute()) as
              | AudioRouteAllOptions
              | string;
            return supportedDevices.has(route as AudioRouteAllOptions)
              ? (route as AudioRouteAllOptions)
              : undefined;
          } catch {
            return undefined;
          }
        })(),
      ]);
      const hardwarePrefersSpeaker =
        hardwareRoute === AudioRouteAllOptions.SpeakerPhone;

      if (!isMounted) {
        return;
      }

      const shouldDefaultToSpeaker =
        typeof hardwareRoute !== 'undefined'
          ? hardwarePrefersSpeaker
          : call.initializationConfig?.cameraEnabled ?? false;

      if (nativeState && nativeState.callingState !== CallingState.IDLE) {
        setSelectedAudioRouteOption(
          hardwareRoute ??
            (shouldDefaultToSpeaker
              ? AudioRouteAllOptions.SpeakerPhone
              : AudioRouteAllOptions.Earpiece),
        );
        setMediaState({
          microphoneEnabled: nativeState.microphoneEnabled,
          cameraEnabled: nativeState.cameraEnabled,
          speakerEnabled: shouldDefaultToSpeaker,
        });
        setInitialAudioRouteSynced(true);
        return;
      }
      const defaultRoute = shouldDefaultToSpeaker
        ? hardwareRoute ?? AudioRouteAllOptions.SpeakerPhone
        : hardwareRoute ?? AudioRouteAllOptions.Earpiece;

      setSelectedAudioRouteOption(defaultRoute);
      setMediaState({
        microphoneEnabled,
        cameraEnabled:
          call.initializationConfig?.cameraEnabled ?? currentCameraEnabled,
        speakerEnabled: shouldDefaultToSpeaker,
      });
      setInitialAudioRouteSynced(true);
    };

    syncInitialMediaState();

    return () => {
      isMounted = false;
    };
  }, [
    call.id,
    call.initializationConfig?.cameraEnabled,
    currentCameraEnabled,
    initialAudioRouteSynced,
    microphoneEnabled,
    setSelectedAudioRouteOption,
  ]);

  useEffect(() => {
    if (initializedCallId === call.id) {
      return;
    }

    const cameraEnabled = Boolean(
      call.initializationConfig?.cameraEnabled ?? initialCameraEnabled,
    );
    const nativeAlreadyJoined = isIos && !isCaller && !joinedFromLink;

    // When joining via link or JoinButton, treat as group call
    const isGroup = joinedFromLink ? true : isGroupCall;

    const startNativeCall = (
      callerName: string,
      isCallerParam: boolean,
      participantUserIds?: number[],
    ) => {
      CallService.startCall({
        id: call.id,
        providerCallId: call.providerCallId,
        cameraEnabled,
        callerName,
        isGroup,
        isCaller: isCallerParam,
        participantUserIds,
      });
    };

    const requestCallPermissions = async () => {
      await ensureMicrophonePermission();
      if (cameraEnabled) {
        await ensureCameraPermission();
      }
    };

    const isRejoiningActiveCall =
      existingCallId === call.id && existingCallingState !== CallingState.IDLE;

    const initializeCall = async () => {
      // Re-entering an already-active call (e.g. tapping the CallStatusBar
      // header after browsing away). Native is still running — starting
      // again would tear it down on Android (see CallManager.startCall).
      if (isRejoiningActiveCall) {
        setInitializedCallId(call.id);
        return;
      }

      // Pause any playing audio/video to avoid audio conflicts
      TrackPlayer.pause();
      setCallingState(
        nativeAlreadyJoined ? CallingState.JOINED : CallingState.JOINING,
      );

      try {
        // Request permissions before joining so tracks are active on the first call.
        // Skip for iOS incoming calls — CallKit already handles permissions.
        if (!nativeAlreadyJoined) {
          await requestCallPermissions();
        }
        // Set group call state for link joins so UI components have correct isGroupCall value
        if (joinedFromLink && !isGroupCall) {
          setGroupCall({isGroupCall: true, callGroupName: ''});
        }

        if (isCaller) {
          // Outgoing call
          const response = await initCall({id: call.id});
          setCallParticipants([
            response.creator,
            ...(response.participants || []),
          ]);
          const participantUserIds = (response.participants || [])
            .filter(p => p.id !== response.creator?.id)
            .map(p => p.id);

          startNativeCall(
            isGroup
              ? groupCall?.callGroupName || 'Call'
              : response.participants?.[0]?.name || 'Call',
            true,
            participantUserIds,
          );
        } else if (nativeAlreadyJoined) {
          // Incoming call - native already accepted via CallKit
          setCallParticipants([call.creator, ...(call.participants || [])]);
          queryClient.setQueryData<Call | undefined>(
            CALLS_QUERY_KEYS.call(call.id),
            callStatusUpdater(CallStatus.Active),
          );
        } else {
          // Android incoming or iOS link join - native will call accept API
          setCallParticipants([call.creator, ...(call.participants || [])]);
          queryClient.setQueryData<Call | undefined>(
            CALLS_QUERY_KEYS.call(call.id),
            callStatusUpdater(CallStatus.Active),
          );

          const participantUserIds = (call.participants || [])
            .filter(p => p.id !== call.creator?.id)
            .map(p => p.id);

          startNativeCall(
            call.creator?.name || 'Call',
            false,
            participantUserIds,
          );
        }

        setCallId(call.id);
        setInitializedCallId(call.id);
      } catch (error) {
        const err = error as {code?: string};
        if (err?.code === AcceptCallErrorCodes.CALL_TOO_MANY_PARTICIPANTS) {
          setShowParticipantsLimitUI(true);
        }
        setIsError(true);
        trackCustomHumandError(error);
      }
    };

    initializeCall();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    call.id,
    ensureCameraPermission,
    ensureMicrophonePermission,
    initializedCallId,
    isCaller,
    joinedFromLink,
  ]);

  useEffect(() => {
    checkBluetoothPermission(setPermissionGranted);

    hasMicrophonePermission().then(granted => {
      if (!granted) {
        setShowMicPermissionDialog(true);
      }
    });
  }, [hasMicrophonePermission]);

  // Disable camera when app goes to background to avoid frozen frame for other participants
  const cameraWasEnabledBeforeBackground = useRef(false);
  const appStateRef = useRef(AppState.currentState);

  useEffect(() => {
    const handleAppStateChange = async (nextAppState: AppStateStatus) => {
      if (
        appStateRef.current === 'active' &&
        nextAppState.match(/inactive|background/)
      ) {
        // Going to background - save camera state and disable if enabled
        if (currentCameraEnabled) {
          cameraWasEnabledBeforeBackground.current = true;
          CallService.setCameraEnabled(false);
        }
      } else if (
        appStateRef.current.match(/inactive|background/) &&
        nextAppState === 'active'
      ) {
        // Coming to foreground - restore camera if it was enabled before
        if (cameraWasEnabledBeforeBackground.current) {
          cameraWasEnabledBeforeBackground.current = false;
          CallService.setCameraEnabled(true);
        }

        // Re-check permissions — user may have granted them in Settings
        if (showMicPermissionDialog) {
          const granted = await hasMicrophonePermission();
          if (granted) {
            setShowMicPermissionDialog(false);
            CallService.setMicrophoneEnabled(true);
          }
        }
      }
      appStateRef.current = nextAppState;
    };

    const subscription = AppState.addEventListener(
      'change',
      handleAppStateChange,
    );
    return () => subscription.remove();
  }, [currentCameraEnabled, hasMicrophonePermission, showMicPermissionDialog]);

  if (showParticipantsLimitUI) {
    return <CallParticipantsLimit />;
  }

  return (
    <>
      {initializedCallId ? (
        <CallProviderContext.Provider
          value={{
            audioRouteOptions,
            setRouteOptions,
            selectedAudioRouteOption,
            setSelectedAudioRouteOption,
            setPermissionGranted,
            permissionGranted,
            callLink,
            maxParticipantsAllowed:
              call.maxParticipantsAllowed ?? ALL_PARTICIPANTS_LIMIT,
          }}>
          {children}
        </CallProviderContext.Provider>
      ) : (
        <CallConnectionFeedback isLoading={!isError} isError={isError} />
      )}
      <DialogMissingCallPermission
        isVisible={showMicPermissionDialog}
        onClose={() => setShowMicPermissionDialog(false)}
      />
    </>
  );
}
