import * as React from 'react';
import { Platform } from 'react-native';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { getBundleId } from 'react-native-device-info';

import type {
  GetEverySurveyAvailableToBeTriggeredQuery,
  SurveyTriggerCustomEvent,
  SurveyTriggerScreen,
} from './__generated__/graphql';
import type { ToldSurveyParam } from './types';
import ToldContext from './ToldContext';
import {
  GET_EVERY_SURVEY_AVAILABLE_TO_BE_TRIGGERED,
  CHECK_CAN_USE_SURVEY,
} from './utils/graphql';
import NavigationListener from './NavigationListener';
import {
  isCustomEventTriggerConditionTrue,
  isScreenTriggerConditionTrue,
} from './utils/triggerConditions';
import ToldWidget from './ToldWidget';
import { isReplied, isSeen, loadSurveyData } from './ToldStorage';

const SERVER_API_URL = 'https://api.told.club';
const WIDGET_URL = 'https://widget.told.club';

type ToldProviderProps = {
  sourceID: string;
  appVersion?: string;
  defaultParams?: ToldSurveyParam[];
  defaultHiddenFields?: { [key: string]: string };
  servers?: { apiUrl: string; widgetUrl: string };
  language?: string;
  children: React.ReactNode;
};

export default function ToldProvider({
  sourceID,
  appVersion,
  defaultParams,
  defaultHiddenFields,
  language,
  servers,
  children,
}: ToldProviderProps) {
  const [loaded, setLoaded] = React.useState(false);
  const [triggers, setTriggers] = React.useState<
    GetEverySurveyAvailableToBeTriggeredQuery['getEverySurveyAvailableToBeTriggered']
  >([]);
  const [currentScreenName, setCurrentScreenName] = React.useState<string>('');
  const [currentSurvey, setCurrentSurvey] = React.useState<{
    surveyId: string;
    sourceID: string;
    params: ToldSurveyParam[];
  } | null>(null);
  const [hiddenFields, setHiddenFields] = React.useState<{
    [key: string]: string;
  }>(defaultHiddenFields || {});

  const [lastLoadVars, setLastLoadVars] = React.useState<object | null>(null);
  const { apiUrl, widgetUrl } = React.useMemo(
    () => ({
      apiUrl: servers?.apiUrl || SERVER_API_URL,
      widgetUrl: servers?.widgetUrl || WIDGET_URL,
    }),
    [servers]
  );

  const client = React.useMemo(
    () =>
      new ApolloClient({
        uri: `${apiUrl}/graphql`,
        cache: new InMemoryCache(),
      }),
    [apiUrl]
  );

  const timeoutsRef = React.useRef<NodeJS.Timeout[]>([]);

  // INIT: Load triggers
  React.useEffect(() => {
    const loadVars = {
      folderID: sourceID,
      type: 'IN_APP_MOBILE',
      os: Platform.OS.toUpperCase(),
      mobileApp: getBundleId(),
      language: language || 'fr',
      version: appVersion || '1.0.0',
      listReplied: [],
      preview: defaultParams?.includes('preview') ? true : false,
    };

    if (
      lastLoadVars &&
      JSON.stringify(lastLoadVars) === JSON.stringify(loadVars)
    )
      return;

    loadSurveyData().then(() => {
      client
        .query({
          query: GET_EVERY_SURVEY_AVAILABLE_TO_BE_TRIGGERED,
          variables: loadVars,
        })
        .then((data) => {
          const { getEverySurveyAvailableToBeTriggered } = data.data;

          if (defaultParams?.includes('debug'))
            console.log(
              `Initializing ${getEverySurveyAvailableToBeTriggered?.length} survey triggers.`
            );

          setLastLoadVars(loadVars);
          setTriggers(getEverySurveyAvailableToBeTriggered);
          setLoaded(true);
        })
        .catch((error) => {
          console.error(
            `Told SDK not loaded correctly : ${error?.message ?? '(unknown)'}`
          );
        });
    });
  }, [lastLoadVars, appVersion, defaultParams, language, sourceID, client]);

  // Method: Start survey
  const start = React.useCallback(
    (surveyId: string) => {
      if (defaultParams?.includes('seeItOnlyOnce') && isSeen(surveyId)) return;

      client
        .query({
          query: CHECK_CAN_USE_SURVEY,
          variables: {
            surveyID: surveyId,
            preview: defaultParams?.includes('preview') ? true : false,
            os: Platform.OS.toUpperCase(),
            mobileApp: getBundleId(),
          },
        })
        .then((data) => {
          const canUse =
            data?.data?.checkIfCanUseWidgetWithSurvey?.canUse ?? false;

          if (canUse) {
            if (defaultParams?.includes('debug'))
              console.log(`Starting survey: ${surveyId}`);
            // Start survey
            setCurrentSurvey({
              surveyId,
              sourceID,
              params: defaultParams || [],
            });
          } else {
            console.log(
              `Cannot display survey ${surveyId}. Check if survey is correctly configured and enabled on website. Or already seen ?`
            );
          }
        })
        .catch((error) => {
          console.error(
            `Told SDK Error while checking if can use widget : ${
              error?.message ?? '(unknown)'
            }`
          );
        });
    },
    [client, defaultParams, sourceID]
  );

  // EVENT: On screen change
  React.useEffect(() => {
    if (!currentScreenName) return;
    if (!loaded) return;

    if (defaultParams?.includes('debug'))
      console.log(`Screen changed to ${currentScreenName}`);

    checkScreenTriggers(currentScreenName);
  }, [currentScreenName, loaded, defaultParams]); // eslint-disable-line react-hooks/exhaustive-deps

  const checkScreenTriggers = React.useCallback(
    (screenName: string, enableDelayCheck = true) => {
      const screenTriggers: SurveyTriggerScreen[] =
        triggers?.filter(
          (el): el is SurveyTriggerScreen =>
            el?.__typename === 'SurveyTriggerScreen'
        ) || [];

      // Clear previously set timeouts
      for (const timeoutId of timeoutsRef.current) clearTimeout(timeoutId);

      for (const trigger of screenTriggers) {
        if (trigger.whenData?.seeItOnlyOnce && isSeen(trigger.survey)) {
          if (defaultParams?.includes('debug'))
            console.log(
              `Survey ${trigger.survey} already seen, not triggering again.`
            );
          continue;
        }
        if (trigger.whenData?.replyOnlyOnce && isReplied(trigger.survey)) {
          if (defaultParams?.includes('debug'))
            console.log(
              `Survey ${trigger.survey} already replied, not triggering again.`
            );
          continue;
        }

        if (
          (trigger.onAllScreen ?? false) ||
          isScreenTriggerConditionTrue(trigger.condition!, screenName)
        ) {
          const delayEnabled = enableDelayCheck
            ? trigger.delay?.active ?? false
            : false;
          const delayValue = delayEnabled ? trigger.delay?.value ?? 0 : 0;

          if (delayEnabled) {
            const timeoutId = setTimeout(() => {
              // Because of js closure, we're using a function to get the current screen name
              setCurrentScreenName((_screenName) => {
                checkScreenTriggers(_screenName, false);
                return _screenName;
              });
            }, delayValue * 1000);
            timeoutsRef.current.push(timeoutId);
          } else {
            if (defaultParams?.includes('debug'))
              console.log(
                `Triggered survey: ${trigger.survey} on screen ${screenName}`
              );
            start(trigger.survey);
            // Start survey
          }
        }
      }
    },
    [start, triggers, defaultParams]
  );

  const onScreenChange = React.useCallback((screenName: string) => {
    setCurrentScreenName(screenName);
  }, []);

  // EVENT: On custom event
  const checkCustomEvent = React.useCallback(
    (
      eventName: string,
      eventProperties?: { [key: string]: string } | undefined
    ) => {
      const customEventTrigger = triggers?.find(
        (el) =>
          el?.__typename === 'SurveyTriggerCustomEvent' &&
          el.eventName === eventName
      ) as SurveyTriggerCustomEvent | undefined;

      if (!customEventTrigger) return;

      // Check whenData conditions
      if (
        customEventTrigger.whenData?.seeItOnlyOnce &&
        isSeen(customEventTrigger.survey)
      ) {
        if (defaultParams?.includes('debug'))
          console.log(
            `Survey ${customEventTrigger.survey} already seen, not triggering again.`
          );
        return;
      }
      if (
        customEventTrigger.whenData?.replyOnlyOnce &&
        isReplied(customEventTrigger.survey)
      ) {
        if (defaultParams?.includes('debug'))
          console.log(
            `Survey ${customEventTrigger.survey} already replied, not triggering again.`
          );
        return;
      }

      const conditions = customEventTrigger.conditions || [];

      let conditionsMet = true;

      // Check if all conditions are met
      for (const condition of conditions) {
        if (condition && condition.key) {
          if (
            !eventProperties?.[condition!.key!] ||
            !isCustomEventTriggerConditionTrue(
              condition!,
              eventProperties?.[condition.key] ?? ''
            )
          )
            conditionsMet = false;
        }
      }

      if (conditionsMet) {
        if (defaultParams?.includes('debug'))
          console.log(
            `Triggered survey: ${customEventTrigger.survey} on custom event ${eventName}`
          );
        start(customEventTrigger.survey);
      }
    },
    [defaultParams, start, triggers]
  );

  // EVENT: On survey close callback
  const onClose = React.useCallback(() => {
    setCurrentSurvey(null);
  }, []);

  // EVENT: Add hidden field
  const addHiddenFields = React.useCallback(
    (
      newHiddenFields: { [key: string]: string } | undefined,
      setData: boolean
    ) => {
      if (setData) {
        // If we want to set the hidden fields, we clear the previous ones
        setHiddenFields(newHiddenFields || {});
      } else {
        // If we want to add hidden fields, we merge them
        setHiddenFields((prevHiddenFields) => ({
          ...prevHiddenFields,
          ...newHiddenFields,
        }));
      }
    },
    []
  );

  return (
    <SafeAreaProvider>
      <ApolloProvider client={client}>
        <NavigationListener onScreenChange={onScreenChange}>
          <ToldContext.Provider
            value={{
              hiddenFields,
              trackScreen: onScreenChange,
              sendCustomEvent: checkCustomEvent,
              close: onClose,
              addHiddenFields,
            }}
          >
            {children}
            {currentSurvey && (
              <ToldWidget {...currentSurvey} widgetUrl={widgetUrl} />
            )}
          </ToldContext.Provider>
        </NavigationListener>
      </ApolloProvider>
    </SafeAreaProvider>
  );
}
