package io.layers.sdk.reactnative;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;

/**
 * Native module for fetching the Google Play install referrer.
 *
 * Uses the Install Referrer API via reflection to avoid a hard dependency on
 * `com.android.installreferrer:installreferrer`. If the library is not present
 * in the host app, the module resolves with null.
 */
public class LayersInstallReferrerModule extends ReactContextBaseJavaModule {

    public LayersInstallReferrerModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return "LayersInstallReferrer";
    }

    /**
     * Read a no-arg {@code long} getter off ReferrerDetails, or 0 when the
     * installed installreferrer version predates it.
     */
    private static long longField(Object details, String getter) {
        try {
            return (Long) details.getClass().getMethod(getter).invoke(details);
        } catch (Exception e) {
            return 0L;
        }
    }

    /** Same, for a {@code String} getter. Missing or null yields "". */
    private static String stringField(Object details, String getter) {
        try {
            Object value = details.getClass().getMethod(getter).invoke(details);
            return value != null ? (String) value : "";
        } catch (Exception e) {
            return "";
        }
    }

    /** Same, for a {@code boolean} getter. Missing yields false. */
    private static boolean booleanField(Object details, String getter) {
        try {
            return (Boolean) details.getClass().getMethod(getter).invoke(details);
        } catch (Exception e) {
            return false;
        }
    }

    @ReactMethod
    public void getInstallReferrer(Promise promise) {
        try {
            // Use reflection to avoid hard dependency on installreferrer library
            Class<?> clientClass = Class.forName(
                    "com.android.installreferrer.api.InstallReferrerClient");
            java.lang.reflect.Method newBuilder = clientClass.getMethod(
                    "newBuilder", android.content.Context.class);
            Object builder = newBuilder.invoke(null, getReactApplicationContext());

            java.lang.reflect.Method buildMethod = builder.getClass().getMethod("build");
            final Object client = buildMethod.invoke(builder);

            // Get the listener interface class
            Class<?> listenerClass = Class.forName(
                    "com.android.installreferrer.api.InstallReferrerStateListener");

            // Guard against double-resolve (endConnection can trigger onDisconnected)
            final java.util.concurrent.atomic.AtomicBoolean resolved =
                    new java.util.concurrent.atomic.AtomicBoolean(false);

            // Create a dynamic proxy for the listener
            Object listener = java.lang.reflect.Proxy.newProxyInstance(
                    listenerClass.getClassLoader(),
                    new Class<?>[]{ listenerClass },
                    (proxy, method, args) -> {
                        if ("onInstallReferrerSetupFinished".equals(method.getName())) {
                            int responseCode = (Integer) args[0];
                            if (responseCode == 0) { // OK
                                try {
                                    java.lang.reflect.Method getDetails = client.getClass()
                                            .getMethod("getInstallReferrer");
                                    Object details = getDetails.invoke(client);

                                    java.lang.reflect.Method getUrl = details.getClass()
                                            .getMethod("getInstallReferrer");
                                    java.lang.reflect.Method getClickTs = details.getClass()
                                            .getMethod("getReferrerClickTimestampSeconds");
                                    java.lang.reflect.Method getInstallTs = details.getClass()
                                            .getMethod("getInstallBeginTimestampSeconds");

                                    String referrerUrl = (String) getUrl.invoke(details);
                                    long clickTs = (Long) getClickTs.invoke(details);
                                    long installTs = (Long) getInstallTs.invoke(details);

                                    WritableMap result = Arguments.createMap();
                                    result.putString("referrerUrl", referrerUrl != null ? referrerUrl : "");
                                    result.putDouble("referrerClickTimestamp", clickTs);
                                    result.putDouble("installBeginTimestamp", installTs);

                                    // The remaining four ReferrerDetails fields are what the
                                    // canonical `install_referrer` event carries alongside the
                                    // three above (schema/fixtures/install-referrer-event.json).
                                    // They arrived in installreferrer 1.1, so each is read
                                    // independently with its own fallback: on an older library
                                    // the JS side still gets a complete map with 0 / "" / false
                                    // rather than losing the whole fetch to one missing method.
                                    result.putDouble("referrerClickTimestampServer",
                                            longField(details, "getReferrerClickTimestampServerSeconds"));
                                    result.putDouble("installBeginTimestampServer",
                                            longField(details, "getInstallBeginTimestampServerSeconds"));
                                    result.putString("installVersion",
                                            stringField(details, "getInstallVersion"));
                                    result.putBoolean("googlePlayInstant",
                                            booleanField(details, "getGooglePlayInstantParam"));
                                    if (resolved.compareAndSet(false, true)) {
                                        promise.resolve(result);
                                    }
                                } catch (Exception e) {
                                    if (resolved.compareAndSet(false, true)) {
                                        promise.resolve(null);
                                    }
                                } finally {
                                    try {
                                        java.lang.reflect.Method endConn = client.getClass()
                                                .getMethod("endConnection");
                                        endConn.invoke(client);
                                    } catch (Exception ignored) {}
                                }
                            } else {
                                if (resolved.compareAndSet(false, true)) {
                                    promise.resolve(null);
                                }
                            }
                        } else if ("onInstallReferrerServiceDisconnected".equals(method.getName())) {
                            // Service disconnected before we could get the referrer
                            if (resolved.compareAndSet(false, true)) {
                                promise.resolve(null);
                            }
                        }
                        return null;
                    }
            );

            java.lang.reflect.Method startConnection = client.getClass()
                    .getMethod("startConnection", listenerClass);
            startConnection.invoke(client, listener);
        } catch (ClassNotFoundException e) {
            // installreferrer library not available
            promise.resolve(null);
        } catch (Exception e) {
            promise.resolve(null);
        }
    }
}
