package com.nadi.player.nativeads;

import android.content.Context;
import android.net.Uri;
import android.util.Log;

import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;

import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.AdLoader;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.LoadAdError;
import com.google.android.gms.ads.MobileAds;
import com.google.android.gms.ads.RequestConfiguration;
import com.google.android.gms.ads.initialization.InitializationStatus;
import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;
import com.google.android.gms.ads.nativead.NativeAd;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * NativeAdsPlugin — Capacitor bridge over Google Mobile Ads SDK Native
 * Advanced.
 *
 * v2 changes (May 2026):
 *   • Buffer JS calls until MobileAds.initialize() finishes. The first
 *     loadNativeAd() request that arrives during cold start no longer
 *     races the SDK and silently no-ops. Pending calls are replayed
 *     once the InitializationStatus arrives.
 *   • Verbose logging — every state transition prints under the
 *     `NativeAdsPlugin` tag so the user can `adb logcat -s
 *     NativeAdsPlugin` and see exactly why an ad did/didn't load.
 *   • If the SDK reports `no fill` for a real Ad Unit, we still log the
 *     LoadAdError code + domain so the publisher can correlate it with
 *     AdMob dashboards (codes 0=internal, 1=invalid_request,
 *     2=network_error, 3=no_fill).
 */
@CapacitorPlugin(name = "NativeAds")
public class NativeAdsPlugin extends Plugin {
    private static final String TAG = "NativeAdsPlugin";

    private final Map<String, NativeAd> cache = new HashMap<>();

    // SDK init state ─────────────────────────────────────────────────
    private boolean sdkReady = false;
    private boolean sdkInitStarted = false;
    private final List<Runnable> pendingAfterInit = new ArrayList<>();

    @Override
    public void load() {
        super.load();
        Log.i(TAG, "load() — bridge attached, kicking off MobileAds.initialize");
        ensureSdkInitialised();
    }

    private void ensureSdkInitialised() {
        if (sdkReady || sdkInitStarted) return;
        sdkInitStarted = true;
        Context ctx = getContext();
        try {
            // Belt + braces: ALWAYS register the emulator + a wildcard
            // test device id BEFORE initialize() so any subsequent ad
            // request honors testing mode regardless of which Ad Unit
            // ID the JS layer eventually passes.
            RequestConfiguration cfg = new RequestConfiguration.Builder()
                .setTestDeviceIds(Arrays.asList(AdRequest.DEVICE_ID_EMULATOR))
                .build();
            MobileAds.setRequestConfiguration(cfg);
            MobileAds.initialize(ctx, new OnInitializationCompleteListener() {
                @Override
                public void onInitializationComplete(InitializationStatus status) {
                    Log.i(TAG, "MobileAds.initialize() complete: "
                        + status.getAdapterStatusMap());
                    sdkReady = true;
                    flushPending();
                }
            });
        } catch (Throwable t) {
            Log.e(TAG, "MobileAds.initialize threw — pending calls will fail", t);
            sdkReady = true; // unblock the queue so JS gets reject() instead of hang
            flushPending();
        }
    }

    private synchronized void flushPending() {
        Log.i(TAG, "flushPending — replaying " + pendingAfterInit.size() + " calls");
        for (Runnable r : pendingAfterInit) {
            try { r.run(); } catch (Throwable t) { Log.w(TAG, "pending run failed", t); }
        }
        pendingAfterInit.clear();
    }

    /**
     * Load a single Native Ad.
     *
     * Args:
     *   adUnitId (string, required)
     *   testing  (boolean, default false)
     */
    @PluginMethod
    public void loadNativeAd(final PluginCall call) {
        final String adUnitId = call.getString("adUnitId");
        final boolean testing = Boolean.TRUE.equals(call.getBoolean("testing", false));
        Log.i(TAG, "loadNativeAd req: adUnitId=" + adUnitId + " testing=" + testing
            + " sdkReady=" + sdkReady);
        if (adUnitId == null || adUnitId.isEmpty()) {
            call.reject("adUnitId is required");
            return;
        }
        if (!sdkReady) {
            // Buffer until init completes (or fails). We saveCall so the
            // PluginCall isn't garbage-collected before we resolve it.
            Log.i(TAG, "  → SDK not ready, queuing call");
            call.setKeepAlive(true);
            synchronized (this) {
                pendingAfterInit.add(new Runnable() {
                    @Override public void run() { doLoad(call, adUnitId, testing); }
                });
            }
            ensureSdkInitialised();
            return;
        }
        doLoad(call, adUnitId, testing);
    }

    private void doLoad(final PluginCall call, final String adUnitId, final boolean testing) {
        final Context ctx = getContext();
        if (testing) {
            try {
                RequestConfiguration cfg = new RequestConfiguration.Builder()
                    .setTestDeviceIds(Arrays.asList(AdRequest.DEVICE_ID_EMULATOR))
                    .build();
                MobileAds.setRequestConfiguration(cfg);
            } catch (Throwable t) {
                Log.w(TAG, "setRequestConfiguration failed (non-fatal)", t);
            }
        }
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    Log.i(TAG, "  → building AdLoader for unit " + adUnitId);
                    AdLoader loader = new AdLoader.Builder(ctx, adUnitId)
                        .forNativeAd(new com.google.android.gms.ads.nativead.NativeAd.OnNativeAdLoadedListener() {
                            @Override
                            public void onNativeAdLoaded(NativeAd nativeAd) {
                                String adId = UUID.randomUUID().toString();
                                cache.put(adId, nativeAd);
                                Log.i(TAG, "  ✓ NativeAd loaded id=" + adId
                                    + " headline=" + nativeAd.getHeadline());

                                JSObject ret = new JSObject();
                                ret.put("adId", adId);
                                ret.put("headline", safe(nativeAd.getHeadline()));
                                ret.put("body", safe(nativeAd.getBody()));
                                ret.put("callToAction", safe(nativeAd.getCallToAction()));
                                ret.put("advertiser", safe(nativeAd.getAdvertiser()));
                                ret.put("store", safe(nativeAd.getStore()));
                                ret.put("price", safe(nativeAd.getPrice()));
                                if (nativeAd.getStarRating() != null) {
                                    ret.put("starRating", nativeAd.getStarRating().doubleValue());
                                }
                                if (nativeAd.getIcon() != null
                                        && nativeAd.getIcon().getUri() != null) {
                                    ret.put("iconUrl", nativeAd.getIcon().getUri().toString());
                                }
                                if (nativeAd.getImages() != null
                                        && !nativeAd.getImages().isEmpty()
                                        && nativeAd.getImages().get(0).getUri() != null) {
                                    Uri u = nativeAd.getImages().get(0).getUri();
                                    ret.put("imageUrl", u.toString());
                                }
                                call.resolve(ret);
                            }
                        })
                        .withAdListener(new AdListener() {
                            @Override
                            public void onAdFailedToLoad(LoadAdError error) {
                                String msg = "no_fill: code=" + error.getCode()
                                    + " domain=" + error.getDomain()
                                    + " msg=" + error.getMessage();
                                Log.w(TAG, "  ✗ " + msg);
                                JSObject details = new JSObject();
                                details.put("code", error.getCode());
                                details.put("domain", error.getDomain());
                                details.put("message", error.getMessage());
                                call.reject(msg, String.valueOf(error.getCode()), details);
                            }
                        })
                        .build();
                    loader.loadAd(new AdRequest.Builder().build());
                } catch (Throwable t) {
                    Log.e(TAG, "loadNativeAd threw", t);
                    call.reject("loadNativeAd_threw: " + t.getMessage());
                }
            }
        });
    }

    @PluginMethod
    public void recordImpression(PluginCall call) {
        String adId = call.getString("adId");
        if (adId == null || !cache.containsKey(adId)) {
            call.reject("unknown adId");
            return;
        }
        JSObject ret = new JSObject();
        ret.put("ok", true);
        ret.put("note", "Impression auto-tracked via NativeAdView only. " +
            "Render via overlay for prod compliance.");
        call.resolve(ret);
    }

    @PluginMethod
    public void recordClick(PluginCall call) {
        String adId = call.getString("adId");
        if (adId == null || !cache.containsKey(adId)) {
            call.reject("unknown adId");
            return;
        }
        NativeAd ad = cache.get(adId);
        try {
            android.os.Bundle b = new android.os.Bundle();
            ad.performClick(b);
            JSObject ret = new JSObject();
            ret.put("ok", true);
            call.resolve(ret);
        } catch (Throwable t) {
            Log.w(TAG, "performClick failed", t);
            call.reject("performClick_failed: " + t.getMessage());
        }
    }

    @PluginMethod
    public void destroy(PluginCall call) {
        String adId = call.getString("adId");
        if (adId == null) {
            call.reject("adId is required");
            return;
        }
        NativeAd removed = cache.remove(adId);
        if (removed != null) {
            try {
                removed.destroy();
            } catch (Throwable t) {
                Log.w(TAG, "destroy failed", t);
            }
        }
        JSObject ret = new JSObject();
        ret.put("ok", true);
        call.resolve(ret);
    }

    private String safe(CharSequence cs) {
        return cs == null ? "" : cs.toString();
    }
}
