package com.withpersona.sdk2.reactnative;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.withpersona.sdk2.inquiry.types.ConstsKt.DEFAULT_REQUEST_KEY;
import static com.withpersona.sdk2.reactnative.InlineInquiryWrapperFragment.ARG_EVENT;

import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.withpersona.sdk2.inquiry.Inquiry;
import com.withpersona.sdk2.inquiry.InquiryResponse;
import com.withpersona.sdk2.inquiry.inline_inquiry.InquiryEvent;
import com.withpersona.sdk2.inquiry.types.collected_data.ErrorCode;

import java.util.Map;

class PersonaInquiryViewManager extends ViewGroupManager<FrameLayout> {
    public static final String REACT_CLASS = "PersonaInquiryView";

    private static final String SDK_CONFIGURATION_ERROR = "The SDK is misconfigured.";

    public final int COMMAND_CREATE = 1;

    @NonNull
    ReactApplicationContext reactContext;

    @Nullable
    private FrameLayout pendingRoot;
    private int pendingViewId = -1;

    public PersonaInquiryViewManager(@NonNull ReactApplicationContext reactContext) {
        this.reactContext = reactContext;
    }

    @NonNull
    @Override
    public String getName() {
        return REACT_CLASS;
    }

    /**
     * Return a FrameLayout which will later hold the Fragment
     */
    @NonNull
    @Override
    public FrameLayout createViewInstance(@NonNull ThemedReactContext reactContext) {
        FrameLayout frameLayout = new FrameLayout(reactContext);
        frameLayout.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        return frameLayout;
    }

    /**
     * Map the "create" command to an integer
     */
    @Nullable
    @Override
    public Map<String, Integer> getCommandsMap() {
        return MapBuilder.of("create", COMMAND_CREATE);
    }

    /**
     * Handle "create" command (called from JS) and call createFragment method
     */
    @Override
    public void receiveCommand(
            @NonNull FrameLayout root,
            int commandId,
            @Nullable ReadableArray args
    ) {
        super.receiveCommand(root, commandId, args);
        int reactNativeViewId = args.getInt(0);

        switch (commandId) {
            case COMMAND_CREATE:
                Object inquiryOptions = root.getTag(R.id.pi2_rn_inquiry_options);
                if (inquiryOptions instanceof ReadableMap) {
                    createFragment(root, reactNativeViewId);
                } else {
                    pendingRoot = root;
                    pendingViewId = reactNativeViewId;
                }
                break;
            default: {}
        }
    }

    @ReactProp(name = "inquiry")
    public void setInquiry(FrameLayout view, @Nullable ReadableMap options) {
        view.setTag(R.id.pi2_rn_inquiry_options, options);

        if (pendingRoot != null && pendingViewId != -1) {
            FrameLayout root = pendingRoot;
            int viewId = pendingViewId;
            pendingRoot = null;
            pendingViewId = -1;
            createFragment(root, viewId);
        }
    }

    @Override
    @NonNull
    public Map getExportedCustomBubblingEventTypeConstants() {
        return MapBuilder.builder()
                .put(
                        "onComplete",
                        MapBuilder.of(
                                "phasedRegistrationNames",
                                MapBuilder.of("bubbled", "onComplete")
                        )
                ).put(
                        "onCanceled",
                        MapBuilder.of(
                                "phasedRegistrationNames",
                                MapBuilder.of("bubbled", "onCanceled")
                        )
                ).put(
                        "onError",
                        MapBuilder.of(
                                "phasedRegistrationNames",
                                MapBuilder.of("bubbled", "onError")
                        )
                ).put(
                        "onEvent",
                        MapBuilder.of(
                                "phasedRegistrationNames",
                                MapBuilder.of("bubbled", "onEvent")
                        )
                ).build();
    }

    /**
     * Replace React Native view with an Inquiry fragment
     */
    public void createFragment(FrameLayout root, int reactNativeViewId) {
        ViewGroup parentView = root.findViewById(reactNativeViewId);
        setupLayout(parentView);

        Object inquiryOptions = root.getTag(R.id.pi2_rn_inquiry_options);
        Inquiry inquiry = null;
        if (inquiryOptions instanceof ReadableMap) {
            inquiry = InquiryUtils.newInquiryFromMap((ReadableMap) inquiryOptions);
        }

        if (inquiry == null) {
            new InquiryResultEmitter(reactContext)
                    .emitResponse(
                            new InquiryResponse.Error(
                                    SDK_CONFIGURATION_ERROR,
                                    ErrorCode.SdkConfigurationError,
                                    "Invalid inquiry arguments."
                            ),
                            root
                    );
            return;
        }

        Fragment inquiryFragment = inquiry
                .buildInlineInquiry()
                .build()
                .createFragment();

        if (inquiryFragment == null) {
            new InquiryResultEmitter(reactContext)
                    .emitResponse(
                            new InquiryResponse.Error(
                                    SDK_CONFIGURATION_ERROR,
                                    ErrorCode.SdkConfigurationError,
                                    "Unable to instantiate fragment."
                            ),
                            root
                    );
            return;
        }

        FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();

        if (activity == null) {
            new InquiryResultEmitter(reactContext)
                    .emitResponse(
                            new InquiryResponse.Error(
                                    SDK_CONFIGURATION_ERROR,
                                    ErrorCode.SdkConfigurationError,
                                    "Unable to get current React Native activity."
                            ),
                            root
                    );
            return;
        }

        final String requestKey = "pi2_rn_inquiry_" + reactNativeViewId;
        InquiryEventEmitter inquiryEventEmitter = new InquiryEventEmitter(reactContext, root.getId());

        activity.getSupportFragmentManager()
            .setFragmentResultListener(DEFAULT_REQUEST_KEY, activity, (reqKey, result) -> {
                InquiryResponse response = Inquiry.extractInquiryResponseFromBundle(result, reactContext);
                new InquiryResultEmitter(reactContext).emitResponse(response, root);
            });
        activity.getSupportFragmentManager()
                .setFragmentResultListener(requestKey, activity,  (reqKey, result) -> {
                    InquiryEvent event = result.getParcelable(ARG_EVENT);

                    if (event != null) {
                        inquiryEventEmitter.emitEvent(event);
                    }
                });

        Fragment wrapperFragment = InlineInquiryWrapperFragment.Companion.newInstance(
                requestKey,
                inquiryFragment
        );

        activity.getSupportFragmentManager()
                .beginTransaction()
                .replace(reactNativeViewId, wrapperFragment, String.valueOf(reactNativeViewId))
                .commit();
        parentView.requestLayout();
    }

    public void setupLayout(View view) {
        Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long frameTimeNanos) {
                manuallyLayoutChildren(view);
                view.getViewTreeObserver().dispatchOnGlobalLayout();
                Choreographer.getInstance().postFrameCallback(this);
            }
        });
    }

    /**
     * Layout all children properly
     */
    public void manuallyLayoutChildren(View view) {
        if (view instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) view;
            for (int i = 0; i < viewGroup.getChildCount(); i++) {
                View child = viewGroup.getChildAt(i);

                child.measure(
                        View.MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(), View.MeasureSpec.EXACTLY),
                        View.MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(), View.MeasureSpec.EXACTLY));

                child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
            }
        }
    }
}