package com.withpersona.sdk2.reactnative;

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

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.withpersona.sdk2.inquiry.ClientThemeSource;
import com.withpersona.sdk2.inquiry.Environment;
import com.withpersona.sdk2.inquiry.ExperimentalInlineApi;
import com.withpersona.sdk2.inquiry.Fields;
import com.withpersona.sdk2.inquiry.Inquiry;
import com.withpersona.sdk2.inquiry.InquiryBuilder;
import com.withpersona.sdk2.inquiry.InquiryTemplateBuilder;
import com.withpersona.sdk2.inquiry.ServerThemeSource;
import com.withpersona.sdk2.inquiry.StyleVariant;
import com.withpersona.sdk2.inquiry.inline_inquiry.InquiryEvent;
import com.withpersona.sdk2.inquiry.types.collected_data.CollectedData;
import com.withpersona.sdk2.inquiry.types.collected_data.DocumentFile;
import com.withpersona.sdk2.inquiry.types.collected_data.GovernmentIdCapture;
import com.withpersona.sdk2.inquiry.types.collected_data.SelfieCapture;
import com.withpersona.sdk2.inquiry.types.collected_data.StepData;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

class InquiryUtils {
    private static final String INQUIRY_ID = "inquiryId";
    private static final String TEMPLATE_ID = "templateId";
    private static final String TEMPLATE_VERSION = "templateVersion";
    private static final String ACCOUNT_ID = "accountId";
    private static final String REFERENCE_ID = "referenceId";
    private static final String ACCESS_TOKEN = "sessionToken";
    private static final String SHARE_TOKEN = "shareToken";
    private static final String ENVIRONMENT = "environment";
    private static final String ENVIRONMENT_ID = "environmentId";
    private static final String FIELDS = "fields";
    private static final String FIELD_ADDITIONAL_FIELDS = "additionalFields";
    private static final String RETURN_COLLECTED_DATA = "returnCollectedData";
    private static final String THEME_SOURCE = "themeSource";
    private static final String THEME_SET_ID = "themeSetId";
    private static final String LOCALE = "locale";
    private static final String STYLE_VARIANT = "styleVariant";

    @Nullable
    protected static Inquiry newInquiryFromMap(ReadableMap options) {
        String inquiryId = options.hasKey(INQUIRY_ID) ? options.getString(INQUIRY_ID) : null;
        String templateId = options.hasKey(TEMPLATE_ID) ? options.getString(TEMPLATE_ID) : null;
        String templateVersion = options.hasKey(TEMPLATE_VERSION) ? options.getString(TEMPLATE_VERSION) : null;

        if (inquiryId != null) {
            InquiryBuilder builder = Inquiry.fromInquiry(inquiryId);

            // Parse styleVariant BEFORE setting theme
            StyleVariant styleVariant = styleVariantFromString(
                options.hasKey(STYLE_VARIANT) ? options.getString(STYLE_VARIANT) : null
            );

            // Determine theme based on styleVariant (defaults to light for backwards compatibility)
            int themeResId = (styleVariant == StyleVariant.DARK)
                ? R.style.Persona_Inquiry2_Theme_Dark
                : R.style.Persona_Inquiry2_Theme;

            String themeSource = options.hasKey(THEME_SOURCE) ? options.getString(THEME_SOURCE) : null;
            builder = builder.theme(themeSource != null && themeSource.equals("server") ?
                new ServerThemeSource(themeResId) : new ClientThemeSource(themeResId));

            String sessionToken = options.hasKey(ACCESS_TOKEN) ? options.getString(ACCESS_TOKEN) : null;
            if (sessionToken != null) {
                builder = builder.sessionToken(sessionToken);
            }

            String shareToken = options.hasKey(SHARE_TOKEN) ? options.getString(SHARE_TOKEN) : null;
            if (shareToken != null) {
                builder = builder.shareToken(shareToken);
            }

            String locale = options.hasKey(LOCALE) ? options.getString(LOCALE) : null;
            if (locale != null) {
                builder = builder.locale(locale);
            }

            // Still pass styleVariant to builder (it might do additional styling)
            if (styleVariant != null) {
                builder = builder.styleVariant(styleVariant);
            }

            return builder.build();
        }

        if (templateId != null || templateVersion != null) {
            InquiryTemplateBuilder builder;
            if (templateId != null) {
                builder = Inquiry.fromTemplate(templateId);
            } else {
                builder = Inquiry.fromTemplateVersion(templateVersion);
            }

            StyleVariant styleVariant = styleVariantFromString(
                options.hasKey(STYLE_VARIANT) ? options.getString(STYLE_VARIANT) : null
            );

            // Determine theme based on styleVariant (defaults to light for backwards compatibility)
            int themeResId = (styleVariant == StyleVariant.DARK)
                ? R.style.Persona_Inquiry2_Theme_Dark
                : R.style.Persona_Inquiry2_Theme;

            String themeSource = options.hasKey(THEME_SOURCE) ? options.getString(THEME_SOURCE) : null;
            builder = builder.theme(themeSource != null && themeSource.equals("server") ?
                new ServerThemeSource(themeResId) : new ClientThemeSource(themeResId));

            String referenceId = options.hasKey(REFERENCE_ID) ? options.getString(REFERENCE_ID) : null;
            if (referenceId != null) {
                builder = builder.referenceId(referenceId);
            }

            String accountId = options.hasKey(ACCOUNT_ID) ? options.getString(ACCOUNT_ID) : null;
            if (accountId != null) {
                builder = builder.accountId(accountId);
            }

            Environment environment = environmentFromString(
                    options.hasKey(ENVIRONMENT) ? options.getString(ENVIRONMENT) : null
            );
            if (environment != null) {
                builder = builder.environment(environment);
            }

            String environmentId = options.hasKey(ENVIRONMENT_ID) ? options.getString(ENVIRONMENT_ID) : null;
            if (environmentId != null) {
                builder = builder.environmentId(environmentId);
            }

            String themeSetId = options.hasKey(THEME_SET_ID) ? options.getString(THEME_SET_ID) : null;
            if (themeSetId != null) {
                builder = builder.themeSetId(themeSetId);
            }

            String locale = options.hasKey(LOCALE) ? options.getString(LOCALE) : null;
            if (locale != null) {
                builder = builder.locale(locale);
            }

            // Still pass styleVariant to builder (it might do additional styling)
            if (styleVariant != null) {
                builder = builder.styleVariant(styleVariant);
            }

            String shareToken = options.hasKey(SHARE_TOKEN) ? options.getString(SHARE_TOKEN) : null;
            if (shareToken != null) {
                builder = builder.shareToken(shareToken);
            }

            ReadableMap fields = options.hasKey(FIELDS) ? options.getMap(FIELDS) : null;

            if (fields != null) {
                Fields.Builder fieldsBuilder = new Fields.Builder();

                for (HashMap.Entry<String, Object> entry : fields.toHashMap().entrySet()) {
                    String key = entry.getKey();
                    Map<String, Object> wrappedValue = (Map<String, Object>) entry.getValue();
                    String type = (String) wrappedValue.get("type");
                    Object value = wrappedValue.get("value");

                    if (value == null) {
                        continue;
                    }

                    if (Objects.equals(type, "string")) {
                        fieldsBuilder.field(key, (String) value);
                    } else if (Objects.equals(type, "integer")) {
                        fieldsBuilder.field(key, ((Double) value).intValue());
                    } else if (Objects.equals(type, "boolean")) {
                        fieldsBuilder.field(key, (Boolean) value);
                    }
                }

                builder = builder.fields(fieldsBuilder.build());
            }

            Boolean returnCollectedData = options.hasKey(RETURN_COLLECTED_DATA) ? options.getBoolean(RETURN_COLLECTED_DATA) : null;
            if (returnCollectedData != null) {
                builder = builder.returnCollectedData(returnCollectedData);
            }

            return builder.build();
        }
        return null;
    }

    protected static ReadableMap wrapField(String type, String value) {
        WritableMap map = Arguments.createMap();
        map.putString("type", type);
        map.putString("value", value);
        return map;
    }

    @Nullable
    protected static ReadableMap collectedDataToMap(@Nullable CollectedData collectedData) {
        if (collectedData == null) {
            return null;
        }

        WritableMap collectedDataMap = Arguments.createMap();

        WritableArray stepDataMap = Arguments.createArray();
        for (StepData stepDatum : collectedData.getStepData()) {
            WritableMap stepDatumMap = Arguments.createMap();
            stepDatumMap.putString("stepName", stepDatum.getStepName());

            if (stepDatum instanceof StepData.DocumentStepData) {
                WritableArray documentsArr = Arguments.createArray();

                for (DocumentFile document : ((StepData.DocumentStepData) stepDatum).getDocuments()) {
                    WritableMap documentMap = Arguments.createMap();
                    documentMap.putString("absoluteFilePath", document.getData().getAbsolutePath());
                    documentsArr.pushMap(documentMap);
                }

                stepDatumMap.putArray("documents", documentsArr);
                stepDatumMap.putString("type", "DocumentStepData");
            } else if (stepDatum instanceof StepData.GovernmentIdStepData) {
                WritableArray capturesArr = Arguments.createArray();

                for (GovernmentIdCapture capture : ((StepData.GovernmentIdStepData) stepDatum).getCaptures()) {
                    WritableMap captureMap = Arguments.createMap();
                    captureMap.putString("idClass", capture.getIdClass());
                    captureMap.putString("captureMethod", capture.getCaptureMethod().name());
                    captureMap.putString("side", capture.getSide().name());

                    WritableArray framesArr = Arguments.createArray();
                    for (GovernmentIdCapture.Frame frame : capture.getFrames()) {
                        WritableMap frameMap = Arguments.createMap();
                        frameMap.putString("absoluteFilePath", frame.getData().getAbsolutePath());

                        framesArr.pushMap(frameMap);
                    }
                    captureMap.putArray("frames", framesArr);

                    capturesArr.pushMap(captureMap);
                }

                stepDatumMap.putArray("captures", capturesArr);
                stepDatumMap.putString("type", "GovernmentIdStepData");
            } else if (stepDatum instanceof StepData.SelfieStepData) {
                StepData.SelfieStepData selfieStepData = (StepData.SelfieStepData) stepDatum;
                ReadableMap centerCaptureMap = selfieCaptureToMap(selfieStepData.getCenterCapture());
                ReadableMap leftCaptureMap = selfieCaptureToMap(selfieStepData.getLeftCapture());
                ReadableMap rightCaptureMap = selfieCaptureToMap(selfieStepData.getRightCapture());

                stepDatumMap.putMap("centerCapture", centerCaptureMap);
                stepDatumMap.putMap("leftCapture", leftCaptureMap);
                stepDatumMap.putMap("rightCapture", rightCaptureMap);
                stepDatumMap.putString("type", "SelfieStepData");
            } else if (stepDatum instanceof StepData.UiStepData) {
                Map<String, Object> componentParams = ((StepData.UiStepData) stepDatum).getComponentParams();

                stepDatumMap.putMap("componentParams", uiStepParamsMapToMap(componentParams));
                stepDatumMap.putString("type", "UiStepData");
            } else {
                // do nothing
            }

            stepDataMap.pushMap(stepDatumMap);
        }

        collectedDataMap.putArray("stepData", stepDataMap);

        return collectedDataMap;
    }

    @OptIn(markerClass = ExperimentalInlineApi.class)
    @Nullable
    protected static ReadableMap inquiryEventToMap(@NonNull InquiryEvent inquiryEvent) {
        WritableMap inquiryEventMap = Arguments.createMap();

        if (inquiryEvent instanceof InquiryEvent.StartEvent) {
            InquiryEvent.StartEvent start = (InquiryEvent.StartEvent) inquiryEvent;
            inquiryEventMap.putString("type", "start");
            inquiryEventMap.putString("inquiryId", start.getInquiryId());
            inquiryEventMap.putString("sessionToken", start.getSessionToken());
        } else if (inquiryEvent instanceof  InquiryEvent.PageChange) {
            InquiryEvent.PageChange pageChange = (InquiryEvent.PageChange) inquiryEvent;
            inquiryEventMap.putString("type", "page_change");
            inquiryEventMap.putString("name", pageChange.getName());
            inquiryEventMap.putString("path", pageChange.getPath());
        } else {
            return null;
        }

        return inquiryEventMap;
    }

    private static Environment environmentFromString(@Nullable String environment) {
        if (environment == null) {
            return null;
        }

        switch (environment) {
            case "production":
                return Environment.PRODUCTION;
            case "sandbox":
                return Environment.SANDBOX;
            default:
                return null;
        }
    }

    private static StyleVariant styleVariantFromString(@Nullable String styleVariant) {
        if (styleVariant == null) {
            return null;
        }

        switch (styleVariant) {
            case "light":
                return StyleVariant.LIGHT;
            case "dark":
                return StyleVariant.DARK;
            default:
                return null;
        }
    }

    private static ReadableMap selfieCaptureToMap(@Nullable SelfieCapture selfieCapture) {
        if (selfieCapture == null) {
            return null;
        }

        WritableMap captureMap = Arguments.createMap();
        captureMap.putString("captureMethod", selfieCapture.getCaptureMethod().name());
        captureMap.putString("absoluteFilePath", selfieCapture.getData().getAbsolutePath());
        return captureMap;
    }

    private static ReadableMap uiStepParamsMapToMap(Map<?, ?> map) {
        WritableMap resultMap = Arguments.createMap();
        for (Map.Entry<?, ?> param : map.entrySet()) {
            Object key = param.getKey();
            Object value = param.getValue();

            if (!(key instanceof String)) {
                continue; // All ui step params should have string keys.
            }

            String keyStr = (String) key;

            if (value instanceof Map<?, ?>) {
                resultMap.putMap(keyStr, uiStepParamsMapToMap((Map<?, ?>) value));
            } else if (value instanceof List<?>) {
                resultMap.putArray(keyStr, uiStepParamsArrToArr((List<?>) value));
            } else if (value instanceof String) {
                resultMap.putString(keyStr, (String) value);
            } else if (value instanceof Boolean) {
                resultMap.putBoolean(keyStr, (Boolean) value);
            } else if (value instanceof Number) {
                resultMap.putDouble(keyStr, (Double) value);
            }
        }

        return resultMap;
    }

    private static ReadableArray uiStepParamsArrToArr(List<?> arr) {
        WritableArray resultArr = Arguments.createArray();

        for (Object element : arr) {
            if (element instanceof String) {
                resultArr.pushString((String) element);
            } else if (element instanceof Boolean) {
                resultArr.pushBoolean((Boolean) element);
            } else if (element instanceof Number) {
                resultArr.pushDouble((Double) element);
            }
        }

        return resultArr;
    }
}
