package com.madstripesdk;

import android.util.Log;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.stripe.stripeterminal.external.callable.Callback;
import com.stripe.stripeterminal.external.callable.Cancelable;
import com.stripe.stripeterminal.external.models.ConnectionStatus;
import com.stripe.stripeterminal.external.models.DiscoveryConfiguration;
import com.stripe.stripeterminal.external.models.DiscoveryMethod;
import com.stripe.stripeterminal.external.models.PaymentIntent;
import com.stripe.stripeterminal.external.models.PaymentMethod;
import com.stripe.stripeterminal.external.models.Reader;
import com.stripe.stripeterminal.external.models.ReaderDisplayMessage;
import com.stripe.stripeterminal.external.models.ReaderSoftwareUpdate;
import com.stripe.stripeterminal.external.models.TerminalException;

import java.util.List;

abstract class BaseMadStripeSdkModule extends ReactContextBaseJavaModule {

  protected List<Reader> currentAvailableReaders;
  protected PaymentIntent currentPaymentIntent;
  protected Cancelable collectPaymentMethodCancelable;
  protected boolean optionalUpdateAvailable;

  public BaseMadStripeSdkModule(ReactApplicationContext context) {
    super(context);
  }

  /**
   * Creates a {@link Callback} object to show if the Terminal was able to get the list of readers.
   *
   * @param callback to notify whether the Terminal finished discovering Readers or not.
   * @return a new Callback instance.
   */
  protected Callback createCallback(Promise callback) {
    return new Callback() {

      /**
       * Triggered when the Terminal successfully scanned for Readers.
       */
      @Override
      public void onSuccess() {
        Log.i(MadStripeConstants.TAG, "Finished discovering Readers.");
        callback.resolve(true);
      }

      /**
       * Triggered when there's an error while scanning for Readers.
       *
       * @param e what the error was.
       */
      @Override
      public void onFailure(@NonNull TerminalException e) {
        Log.e(MadStripeConstants.TAG, "Callback error.", e);
        callback.reject(e);
      }
    };
  }

  /**
   * Creates a {@link DiscoveryConfiguration} object, based on the provided configuration parameters.
   *
   * @param discoveryMethod the way you want to discover new {@link Reader}s.
   * @param simulated       whether to connect to a simulated {@link Reader} or not.
   * @return an instance of a {@link DiscoveryConfiguration}.
   */
  protected DiscoveryConfiguration createDiscoveryConfiguration(DiscoveryMethod discoveryMethod,
                                                                boolean simulated) {
    return new DiscoveryConfiguration(0, discoveryMethod, simulated);
  }

  /**
   * Creates a proper {@link DiscoveryMethod} to use in the newly created {@link DiscoveryConfiguration}.
   *
   * @param constant an {@link Integer}, from 0 to 4, used to get a {@link DiscoveryMethod} in the order
   *                 they were declared. The default return value is Bluetooth Scan.
   * @return a proper enum value for the Discovery Configuration to use.
   */
  protected DiscoveryMethod getDiscoveryMethodFromInt(int constant) {
    DiscoveryMethod method = DiscoveryMethod.BLUETOOTH_SCAN;
    switch (constant) {
      case 1:
        method = DiscoveryMethod.BLUETOOTH_SCAN;
        break;
      case 2:
        method = DiscoveryMethod.INTERNET;
        break;
      case 3:
        method = DiscoveryMethod.HANDOFF;
        break;
      case 4:
        method = DiscoveryMethod.EMBEDDED;
        break;
    }
    return method;
  }

  /**
   * Emits an empty event.
   *
   * @param eventName the id for the event.
   */
  protected void sendEvent(String eventName) {
    getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
      .emit(eventName, null);
    Log.i(MadStripeConstants.TAG, "Event [ " + eventName + " ] sent.");
  }

  /**
   * Emits a String event. Json, for example.
   *
   * @param eventName the id for the event.
   */
  protected void sendEvent(String eventName, String param) {
    getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
      .emit(eventName, param);
    Log.i(MadStripeConstants.TAG, "Event [ " + eventName + " ] sent.");
  }

  /**
   * Emits an event.
   *
   * @param eventName the id for the event.
   * @param params    a {@link WritableMap} to handle parameters for the event.
   */
  protected void sendEvent(String eventName, WritableMap params) {
    getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
      .emit(eventName, params);
    Log.i(MadStripeConstants.TAG, "Event [ " + eventName + " ] sent.");
  }

  /**
   * Emits an event.
   *
   * @param eventName the id for the event.
   * @param params    a {@link WritableArray} to handle parameters for the event.
   */
  protected void sendEvent(String eventName, WritableArray params) {
    getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
      .emit(eventName, params);
    Log.i(MadStripeConstants.TAG, "Event [ " + eventName + " ] sent.");
  }

  /**
   * Finds a {@link Reader} by it's serial number.
   *
   * @param serialNumber the serial to look for
   * @return the {@link Reader} where we'll connect to.
   */
  protected Reader findReaderBySerialNumber(String serialNumber) {
    Reader result = null;
    for (Reader r : this.currentAvailableReaders) {
      if (null != r.getSerialNumber()) {
        if (r.getSerialNumber().compareToIgnoreCase(serialNumber) == 0) {
          result = r;
        }
      }
    }
    return result;
  }

  /**
   * Finds the {@link ConnectionStatus} matching integer.
   *
   * @param status the status to convert
   * @return it's integer equivalent.
   */
  protected int findStatusByConnectionStatus(ConnectionStatus status) {
    int intStatus = 0;
    switch (status) {
      case NOT_CONNECTED:
        break;
      case CONNECTING:
        intStatus = 2;
        break;
      case CONNECTED:
        intStatus = 1;
        break;
    }
    return intStatus;
  }

  /**
   * Finds the {@link ReaderDisplayMessage} matching integer.
   *
   * @param readerDisplayMessage the message to convert.
   * @return it's integer equivalent.
   */
  protected int findMessageByDisplayMessage(ReaderDisplayMessage readerDisplayMessage) {
    int message = 0;
    switch (readerDisplayMessage) {
      case CHECK_MOBILE_DEVICE:
      case RETRY_CARD:
        break;
      case INSERT_CARD:
        message = 1;
        break;
      case INSERT_OR_SWIPE_CARD:
        message = 2;
        break;
      case SWIPE_CARD:
        message = 3;
        break;
      case REMOVE_CARD:
        message = 4;
        break;
      case MULTIPLE_CONTACTLESS_CARDS_DETECTED:
        message = 5;
        break;
      case TRY_ANOTHER_READ_METHOD:
        message = 6;
        break;
      case TRY_ANOTHER_CARD:
        message = 7;
        break;
    }
    return message;
  }

}
