package com.feedbackreactnativesdk;

import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;

import java.util.HashMap;

import co.pisano.feedback.data.helper.ActionListener;
import co.pisano.feedback.data.helper.PisanoActions;
import co.pisano.feedback.data.helper.ViewMode;
import co.pisano.feedback.data.model.Title;
import co.pisano.feedback.managers.PisanoSDK;
import co.pisano.feedback.managers.PisanoSDKManager;

public class FeedbackReactNativeSdkModuleImpl {

  public static final String NAME = "FeedbackReactNativeSdk";
  private static final String TAG = "FeedbackRNSdk";

  private static Callback bootCallback = null;
  private static Callback showCallback = null;
  private static boolean debugEnabled = false;

  public static void debugMode(boolean debugMode) {
    debugEnabled = debugMode;
  }

  /**
   * Maps PisanoActions to the JS feedbackSDKCallback enum index.
   * Must stay in sync with feedbackSDKCallback in index.tsx.
   */
  private static int pisanoActionToJsCallbackIndex(@NonNull PisanoActions action) {
    switch (action) {
      case CLOSED:                    return 1;
      case SEND_FEEDBACK:             return 2;
      case OUTSIDE:                   return 3;
      case OPENED:                    return 4;
      case DISPLAY_ONCE:              return 5;
      case PREVENT_MULTIPLE_FEEDBACK: return 6;
      case CHANNEL_QUOTA_EXCEEDED:    return 7;
      case DISPLAY_RATE_LIMITED:      return 8;
      case CHANNEL_PASSIVE:           return 9;
      case INIT_FAILED:               return 10;
      default:                        return 0;
    }
  }

  private static void invokeOnce(@Nullable Callback cb, Object value) {
    if (cb == null) return;
    try {
      cb.invoke(value);
    } catch (Exception e) {
      Log.e(TAG, "Callback invocation error: " + e.toString());
    }
  }

  private static boolean isInitAction(@NonNull PisanoActions action) {
    return action == PisanoActions.INIT_SUCCESS || action == PisanoActions.INIT_FAILED;
  }

  private static final ActionListener sdkActionListener = new ActionListener() {
    @Override
    public void action(@NonNull PisanoActions pisanoActions) {
      String desc = pisanoActions.getActionDescription();
      Log.d(TAG, "ActionListener: " + desc);

      if (isInitAction(pisanoActions)) {
        Callback cb = bootCallback;
        bootCallback = null;
        invokeOnce(cb, desc);
      } else {
        Callback cb = showCallback;
        if (cb != null) {
          showCallback = null;
          invokeOnce(cb, pisanoActionToJsCallbackIndex(pisanoActions));
        } else {
          Log.w(TAG, "ActionListener fired (" + desc + ") but no callback registered.");
        }
      }
    }
  };

  public static void boot(
      ReactApplicationContext context,
      @NonNull String appId,
      @NonNull String accessKey,
      @NonNull String code,
      @NonNull String apiUrl,
      @NonNull String feedbackUrl,
      @Nullable String eventUrl,
      @Nullable Callback callback) {

    if (callback != null) {
      bootCallback = callback;
    }

    final String safeEventUrl = (eventUrl != null && !eventUrl.trim().isEmpty())
        ? eventUrl.trim()
        : null;

    Activity currentActivity = context.getCurrentActivity();
    if (currentActivity == null) {
      Log.e(TAG, "boot: getCurrentActivity() returned null");
      Callback cb = bootCallback;
      bootCallback = null;
      invokeOnce(cb, "The SDK init is failed.");
      return;
    }

    PisanoSDKManager.Builder builder = new PisanoSDKManager.Builder(currentActivity)
        .setApplicationId(appId)
        .setAccessKey(accessKey)
        .setCode(code)
        .setApiUrl(apiUrl)
        .setFeedbackUrl(feedbackUrl)
        .setDebug(debugEnabled);

    if (safeEventUrl != null) {
      builder.setEventUrl(safeEventUrl);
    }

    PisanoSDKManager pisanoSDKManager = builder
        .setCloseStatusCallback(sdkActionListener)
        .build();

    new Handler(Looper.getMainLooper()).post(() -> {
      try {
        PisanoSDK.INSTANCE.init(pisanoSDKManager);
      } catch (Exception e) {
        Log.e(TAG, "PisanoSDK.init() threw: " + e.toString());
        Callback cb = bootCallback;
        bootCallback = null;
        invokeOnce(cb, "The SDK init is failed.");
      }
    });
  }

  public static void show(
      Integer viewMode,
      @Nullable String title,
      @Nullable String titleFontSize,
      @Nullable String code,
      @Nullable String language,
      @Nullable ReadableMap customer,
      @Nullable ReadableMap payload,
      @Nullable Callback callback) {

    try {
      showCallback = callback;

      HashMap<String, String> payloadHashMap =
          PisanoCustomerHelper.convertReadableMapToHashMap(payload);

      ViewMode flowViewMode = ViewMode.parse(viewMode);
      Title customTitle = null;
      if (title != null && !title.isEmpty()) {
        customTitle = new Title(
            title,
            titleFontSize != null ? Float.parseFloat(titleFontSize) : null,
            null, null, null, null);
      }

      PisanoSDK.INSTANCE.show(
          flowViewMode,
          customTitle,
          language,
          payloadHashMap,
          PisanoCustomerHelper.convertToPisanoCustomer(customer),
          code);
    } catch (Exception e) {
      Log.e(TAG, "show error: " + e.toString());
    }
  }

  public static void show(
      double viewMode,
      @Nullable String title,
      @Nullable Double titleFontSize,
      @Nullable String code,
      @Nullable String language,
      @Nullable ReadableMap customer,
      @Nullable ReadableMap payload,
      @Nullable Callback callback) {
    Float size = titleFontSize != null ? titleFontSize.floatValue() : null;
    show((int) viewMode, title, size != null ? String.valueOf(size) : null,
        code, language, customer, payload, callback);
  }

  public static void clear() {
    PisanoSDK.INSTANCE.clearAction();
  }
}
