import {
  EvervaultProvider,
  EvervaultProviderProps,
  ThreeDSecure,
  useThreeDSecure,
} from '@evervault/react-native';
import React from 'react';
import { Modal } from 'react-native';
import {
  Create3DSecureSessionCallbacks,
  Create3DSSessionPayload,
} from './lib/types';
import { create3DSSessionInDuffelAPI } from './lib/create3DSSessionInDuffelAPI';
import { refresh3DSSessionInDuffelAPI } from './lib/refreshThreeDSecureSession';
import { getLiveModeFromClientKey } from './lib/getLiveModeFromClientKey';
import { getDefaultProviderIds } from './lib/getDefaultProviderIds';

/////////////
// CONTEXT //
/////////////
type Create3DSecureSession = (
  payload: Create3DSSessionPayload,
  { onSuccess, onFailure, onError }: Create3DSecureSessionCallbacks,
  overrideDuffelAPIUrl?: string
) => Promise<void>;

const DuffelThreeDSecureContext = React.createContext<{
  create3DSecureSession: Create3DSecureSession;
}>({ create3DSecureSession: async () => {} });

/////////////////////
// COMPONENT PROPS //
/////////////////////
export interface DuffelThreeDSecureProviderProps {
  children: React.ReactNode;

  /**
   * The provider IDs for the Evervault provider.
   * This is used to identify the Evervault team and app that will be used for 3DS.
   * A default value exists, but you may override them with this prop.
   */
  providerIds?: Pick<EvervaultProviderProps, 'teamId' | 'appId'>;

  clientKey: string;
}

interface DuffelThreeDSecureProviderWithEvervaultProps {
  children: React.ReactNode;
  clientKey: string;
}

//////////////////////
// PUBLIC INTERFACE //
//////////////////////

export function DuffelThreeDSecureProvider({
  children,
  clientKey,
  providerIds,
}: DuffelThreeDSecureProviderProps) {
  const liveMode = getLiveModeFromClientKey(clientKey);
  let { teamId, appId } = getDefaultProviderIds(liveMode);

  if (providerIds) {
    teamId = providerIds.teamId;
    appId = providerIds.appId;
  }

  return (
    <EvervaultProvider teamId={teamId} appId={appId}>
      <DuffelThreeDSecureProviderWithEvervault clientKey={clientKey}>
        {children}
      </DuffelThreeDSecureProviderWithEvervault>
    </EvervaultProvider>
  );
}

export function useCreate3DSecureSession() {
  if (!React.useContext(DuffelThreeDSecureContext)) {
    throw new Error(
      'useCreate3DSecureSession must be used within a DuffelThreeDSecureProvider'
    );
  }

  return React.useContext(DuffelThreeDSecureContext);
}
export type UseCreate3DSecureSessionHook = typeof useCreate3DSecureSession;

////////////////////////////////////////
// `create3DSecureSession` DEFINITION //
////////////////////////////////////////
function DuffelThreeDSecureProviderWithEvervault({
  children,
  clientKey,
}: DuffelThreeDSecureProviderWithEvervaultProps) {
  const tds = useThreeDSecure();

  const create3DSecureSession = async (
    payload: Create3DSSessionPayload,
    { onSuccess, onFailure, onError }: Create3DSecureSessionCallbacks,
    // overrideDuffelAPIUrl should only be used for Duffel internal development.
    _overrideDuffelAPIUrl?: string
  ) => {
    if (_overrideDuffelAPIUrl) {
      console.warn(
        'overrideDuffelAPIUrl should not be used in production. It is only intended for Duffel internal development.'
      );
    }
    // Create a 3DS session on your backend
    create3DSSessionInDuffelAPI(clientKey, payload, _overrideDuffelAPIUrl)
      .then((session) => {
        if (session.status === 'ready_for_payment') {
          onSuccess(session);
          return;
        }

        if (session.client_id === null) {
          onError(new Error('Failed to create 3DS session'));
          return;
        }

        // when it goes well, you can call Duffel to update the 3DS session
        tds.start(session.client_id, {
          onFailure,
          onError,
          onSuccess: () => {
            refresh3DSSessionInDuffelAPI(
              clientKey,
              session.id,
              _overrideDuffelAPIUrl
            )
              .then(onSuccess)
              .catch(onFailure);
          },
        });
      })
      .catch((error) => {
        onError(error);
      });
  };

  return (
    <DuffelThreeDSecureContext.Provider value={{ create3DSecureSession }}>
      {children}
      <ThreeDSecure state={tds} key={tds.session?.sessionId}>
        <Modal onRequestClose={() => tds.cancel()}>
          <ThreeDSecure.Frame />
        </Modal>
      </ThreeDSecure>
    </DuffelThreeDSecureContext.Provider>
  );
}
