package com.nadi.player.nativeplayer;

import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;

import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.nadi.player.nativeplayer.channels.ChannelStore;
import com.nadi.player.nativeplayer.lastplayed.LastPlayedStore;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

@CapacitorPlugin(name = "NativePlayer")
public class NativePlayerPlugin extends Plugin {

    /** Process-singleton handle so {@link PlayerActivity} (which is a
     *  separate Activity, NOT owned by this plugin) can push the
     *  "playerExited" event back into the JS WebView. Capacitor only
     *  ever creates one Plugin instance per app — assigning in
     *  {@link #load()} is safe. */
    private static NativePlayerPlugin INSTANCE;

    @Override
    public void load() {
        super.load();
        INSTANCE = this;
    }

    /** Called from {@link PlayerActivity#teardownPlayback()} once the
     *  native ExoPlayer has been stopped and the foreground service
     *  killed. Drives the JS-side {@code useExitInterstitial} hook
     *  which counts player-exits and shows an interstitial every Nth
     *  time (currently every 3rd, gated by ad-free entitlement). */
    public static void notifyPlayerExited() {
        if (INSTANCE == null) return;
        try {
            INSTANCE.notifyListeners("playerExited", new JSObject());
        } catch (Throwable t) {
            android.util.Log.w("NativePlayer", "notifyPlayerExited failed", t);
        }
    }

    /** Preferred IPTV / video player packages, in priority order. */
    private static final List<String> PREFERRED_PACKAGES = Arrays.asList(
        "com.vidoplay.player",               // VidoPlay (default / recommended)
        "org.videolan.vlc",                  // VLC for Android
        "com.mxtech.videoplayer.pro",        // MX Player Pro
        "com.mxtech.videoplayer.ad",         // MX Player (free)
        "com.brouken.player",                // Just Player
        "com.ytv.player",                    // YTV Player
        "com.ytvplayer.app",
        "com.xtremeiptv.xtreamplayeriptv",
        "com.nst.iptvsmarterstvbox",
        "com.nst.iptvsmarters",
        "net.gtvbox.videoplayer"
    );

    // ─────────────────────────────────────────────────────────────
    //  play  — in-app ExoPlayer
    //
    //  JS call shape:
    //    NativePlayer.play({
    //      url:        "http://portal/stream.m3u8",   // required
    //      title:      "ESPN HD",
    //      isLive:     true,                          // false = VOD
    //      userAgent:  "MyUA/1.0",
    //      headers:    { Referer: "https://x.com" },
    //
    //      // Live TV only: full channel catalogue for the
    //      // right-side overlay. Ignored for VOD (the overlay is
    //      // hidden in that case). One round-trip = one push.
    //      channels:   [
    //        {
    //          number:    101,
    //          name:      "ESPN HD",
    //          category:  "Sports",
    //          streamUrl: "http://portal/stream/101.m3u8",
    //          epgNow:    "Premier League...",
    //          epgNext:   "22:00 Match of the Day",
    //          favorite:  true,
    //          userAgent: "...",          // optional, per-channel UA
    //          headers:   { ... },        // optional, per-channel headers
    //          logoUrl:   "https://..."   // optional, ignored for now
    //        },
    //        ...
    //      ],
    //      currentChannelNumber: 101      // optional, highlights row
    //    })
    // ─────────────────────────────────────────────────────────────
    @PluginMethod
    public void play(PluginCall call) {
        String url = call.getString("url");
        if (url == null || url.isEmpty()) {
            call.reject("'url' is required");
            return;
        }

        String userAgent = call.getString(
            "userAgent",
            "IPTVSmartersPlayer/3.1.6 (Linux; Android) ExoPlayerLib/2.19.1"
        );
        String title = call.getString("title", "");
        boolean isLive = Boolean.TRUE.equals(call.getBoolean("isLive", true));

        // ── Right-side channel overlay hand-off ─────────────────────
        // For Live TV, the React layer hands us the full channel list
        // (already parsed from Xtream/M3U) and an optional current
        // channel number. For VOD (movies / series) we deliberately
        // clear the store so the overlay UI never appears.
        if (isLive) {
            JSArray channels = call.getArray("channels");
            if (channels != null && channels.length() > 0) {
                ChannelStore.get().setFromJson(channels);
            }
            // Nothing else uses currentChannelNumber yet, but accepting it
            // here means JS can deep-link straight to a channel later
            // without an extra round-trip.
            Integer curNum = call.getInt("currentChannelNumber");
            if (curNum != null) {
                ChannelStore.get().setCurrentChannelNumber(curNum);
            }
        } else {
            // VOD: wipe any leftover live-TV catalogue so the overlay
            // can't be opened on a movie/series session.
            ChannelStore.get().setFromJson(new JSArray());
            ChannelStore.get().setCurrentChannelNumber(-1);
        }

        JSObject headersObj = call.getObject("headers");
        String[] headerKeys = new String[0];
        String[] headerVals = new String[0];
        if (headersObj != null) {
            try {
                java.util.List<String> ks = new java.util.ArrayList<>();
                java.util.List<String> vs = new java.util.ArrayList<>();
                Iterator<String> it = headersObj.keys();
                while (it.hasNext()) {
                    String k = it.next();
                    Object v = headersObj.get(k);
                    if (v != null) {
                        ks.add(k);
                        vs.add(String.valueOf(v));
                    }
                }
                headerKeys = ks.toArray(new String[0]);
                headerVals = vs.toArray(new String[0]);
            } catch (JSONException e) {
                // ignore
            }
        }

        Intent intent = new Intent(getContext(), PlayerActivity.class);
        intent.putExtra(PlayerActivity.EXTRA_URL, url);
        intent.putExtra(PlayerActivity.EXTRA_USER_AGENT, userAgent);
        intent.putExtra(PlayerActivity.EXTRA_TITLE, title);
        intent.putExtra(PlayerActivity.EXTRA_IS_LIVE, isLive);
        intent.putExtra(PlayerActivity.EXTRA_HEADER_KEYS, headerKeys);
        intent.putExtra(PlayerActivity.EXTRA_HEADER_VALS, headerVals);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        getContext().startActivity(intent);

        JSObject ret = new JSObject();
        ret.put("launched", true);
        ret.put("via", "exoplayer");
        call.resolve(ret);
    }

    // ─────────────────────────────────────────────────────────────
    //  openExternal  — hand off to VidoPlay / VLC / MX / chooser
    //
    //  Resolution order:
    //   1. If the caller forced a specific `package`, use it.
    //      If that package isn't installed, respond with
    //      { launched: false, reason: "not-installed", package }
    //      so the UI can offer "Install from Play Store".
    //      (We no longer auto-fall-back to another app here — that
    //       surprised users who had intentionally picked VidoPlay.)
    //   2. Otherwise, try the preferred-package list in order.
    //   3. Otherwise, show the system chooser.
    //   4. Otherwise, fall back to in-app ExoPlayer so the user
    //      always gets video, never a dead-end.
    // ─────────────────────────────────────────────────────────────
    @PluginMethod
    public void openExternal(PluginCall call) {
        String url = call.getString("url");
        if (url == null || url.isEmpty()) {
            call.reject("'url' is required");
            return;
        }
        String userAgent = call.getString(
            "userAgent",
            "IPTVSmartersPlayer/3.1.6 (Linux; Android) ExoPlayerLib/2.19.1"
        );
        String title = call.getString("title", "");
        String forcePackage = call.getString("package", null);

        String mime = guessMime(url);
        Intent base = buildVideoIntent(url, mime, userAgent, title);
        PackageManager pm = getContext().getPackageManager();

        // 1) Caller forced a specific package → try just that one.
        if (forcePackage != null && !forcePackage.isEmpty()
                && !"chooser".equalsIgnoreCase(forcePackage)) {
            Intent direct = new Intent(base);
            direct.setPackage(forcePackage);

            // Not installed → let the UI offer the Play Store.
            if (direct.resolveActivity(pm) == null) {
                JSObject ret = new JSObject();
                ret.put("launched", false);
                ret.put("reason", "not-installed");
                ret.put("package", forcePackage);
                call.resolve(ret);
                return;
            }

            try {
                getContext().startActivity(direct);
                resolveOk(call, "package:" + forcePackage);
                return;
            } catch (ActivityNotFoundException e) {
                JSObject ret = new JSObject();
                ret.put("launched", false);
                ret.put("reason", "activity-not-found");
                ret.put("package", forcePackage);
                call.resolve(ret);
                return;
            }
        }

        // 2) No package forced → try the preferred list.
        for (String pkg : PREFERRED_PACKAGES) {
            Intent direct = new Intent(base);
            direct.setPackage(pkg);
            if (direct.resolveActivity(pm) != null) {
                try {
                    getContext().startActivity(direct);
                    resolveOk(call, "package:" + pkg);
                    return;
                } catch (Exception ignored) {
                    // try next
                }
            }
        }

        // 3) System chooser ("Open with…")
        List<ResolveInfo> resolvers = pm.queryIntentActivities(base, 0);
        if (resolvers != null && !resolvers.isEmpty()) {
            Intent chooser = Intent.createChooser(base, "Open stream with…");
            chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            getContext().startActivity(chooser);
            resolveOk(call, "chooser");
            return;
        }

        // 4) Absolutely nothing installed → fall back to in-app ExoPlayer.
        play(call);
    }

    // ─────────────────────────────────────────────────────────────
    //  openPlayStore  — let UI offer "Install <external app>"
    // ─────────────────────────────────────────────────────────────
    @PluginMethod
    public void openPlayStore(PluginCall call) {
        String pkg = call.getString("package");
        if (pkg == null || pkg.isEmpty()) {
            call.reject("'package' is required");
            return;
        }
        try {
            Intent market = new Intent(Intent.ACTION_VIEW,
                    Uri.parse("market://details?id=" + pkg));
            market.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            getContext().startActivity(market);
            resolveOk(call, "market");
        } catch (ActivityNotFoundException e) {
            // No Play Store → open in browser.
            Intent web = new Intent(Intent.ACTION_VIEW,
                    Uri.parse("https://play.google.com/store/apps/details?id=" + pkg));
            web.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            getContext().startActivity(web);
            resolveOk(call, "web");
        }
    }

    // ─────────────────────────────────────────────────────────────
    //  isInstalled — UI can hide "Install" if the app is already here
    // ─────────────────────────────────────────────────────────────
    @PluginMethod
    public void isInstalled(PluginCall call) {
        String pkg = call.getString("package");
        JSObject ret = new JSObject();
        if (pkg == null || pkg.isEmpty()) {
            ret.put("installed", false);
            call.resolve(ret);
            return;
        }
        try {
            getContext().getPackageManager().getPackageInfo(pkg, 0);
            ret.put("installed", true);
        } catch (PackageManager.NameNotFoundException e) {
            ret.put("installed", false);
        }
        call.resolve(ret);
    }

    // ─────────────────────────────────────────────────────────────
    //  listInstalledPlayers  — debug / UI hint
    // ─────────────────────────────────────────────────────────────
    @PluginMethod
    public void listInstalledPlayers(PluginCall call) {
        JSObject ret = new JSObject();
        org.json.JSONArray arr = new org.json.JSONArray();
        PackageManager pm = getContext().getPackageManager();
        Intent probe = new Intent(Intent.ACTION_VIEW);
        probe.setDataAndType(Uri.parse("http://example.com/stream.m3u8"),
                "application/x-mpegURL");
        for (ResolveInfo r : pm.queryIntentActivities(probe, 0)) {
            JSObject item = new JSObject();
            item.put("packageName", r.activityInfo.packageName);
            item.put("label", String.valueOf(r.loadLabel(pm)));
            arr.put(item);
        }
        ret.put("players", arr);
        call.resolve(ret);
    }

    @PluginMethod
    public void isAvailable(PluginCall call) {
        JSObject ret = new JSObject();
        ret.put("available", true);
        ret.put("engine", "ExoPlayer");
        call.resolve(ret);
    }

    // ─────────────────────────────────────────────────────────────
    //  Last-played channel: powers the "Resume {name}" toast on the
    //  LiveTV page. The native side persists a small JSON blob in
    //  SharedPreferences every time a Live channel starts (initial
    //  launch + every zap). JS reads it on cold start and offers a
    //  one-tap resume.
    //
    //  JS shape returned by getLastPlayed():
    //    { hasLastPlayed: true,
    //      title: "ESPN HD",
    //      url:   "http://portal/stream/101.m3u8",
    //      isLive: true,
    //      userAgent: "...",
    //      channelNumber: 101,
    //      profileId: "abc",          // optional
    //      savedAt: 1714060800000 }   // epoch millis
    //  …or { hasLastPlayed: false } when nothing is saved.
    // ─────────────────────────────────────────────────────────────
    @PluginMethod
    public void getLastPlayed(PluginCall call) {
        String raw = LastPlayedStore.getRawJson(getContext());
        JSObject ret = new JSObject();
        if (raw == null || raw.isEmpty()) {
            ret.put("hasLastPlayed", false);
            call.resolve(ret);
            return;
        }
        try {
            JSONObject o = new JSONObject(raw);
            ret.put("hasLastPlayed", true);
            ret.put("title", o.optString("title", ""));
            ret.put("url", o.optString("url", ""));
            ret.put("isLive", o.optBoolean("isLive", true));
            ret.put("userAgent", o.optString("userAgent", ""));
            ret.put("channelNumber", o.optInt("channelNumber", -1));
            ret.put("profileId", o.optString("profileId", ""));
            ret.put("savedAt", o.optLong("savedAt", 0L));
        } catch (JSONException e) {
            ret.put("hasLastPlayed", false);
        }
        call.resolve(ret);
    }

    @PluginMethod
    public void clearLastPlayed(PluginCall call) {
        LastPlayedStore.clear(getContext());
        JSObject ret = new JSObject();
        ret.put("cleared", true);
        call.resolve(ret);
    }

    /**
     * BUG FIX (2026-01) — "Clear Cache on logout".
     *
     * Wipes every native storage layer so the next user signing in on
     * the same device starts with a perfectly clean slate. Specifically:
     *
     *   1. ALL SharedPreferences files except a small allow-list
     *      (language pick + stable device-id) — destroys cached
     *      Xtream credentials, last-played channel, channel-store
     *      snapshot, AdMob plugin caches, etc.
     *   2. The Capacitor WebView's HTTP cache + cookie store (so any
     *      lingering session cookies for the iptv portal disappear).
     *   3. The app's internal cache directory (DiskLruCache files,
     *      ExoPlayer download cache fragments, etc.).
     *
     * PRESERVED keys (re-seeded after the wipe inside Capacitor's
     * default prefs file):
     *   • nadiplay_lang, nadiplay_lang_chosen
     *   • nadiplay_device_id
     */
    @PluginMethod
    public void clearAppCache(PluginCall call) {
        android.content.Context ctx = getContext().getApplicationContext();

        // ── (a) Snapshot allow-listed keys from Capacitor's default
        //         prefs file BEFORE wiping. Capacitor's @capacitor/preferences
        //         plugin stores everything in "CapacitorStorage" by default,
        //         but our React layer uses plain localStorage which
        //         Capacitor mirrors to "CapWebViewSettings". We snapshot
        //         from every prefs file in the app's shared_prefs dir
        //         to cover both. ────────────────────────────────────
        String[] preserveKeys = new String[] {
            "nadiplay_lang",
            "nadiplay_lang_chosen",
            "nadiplay_device_id",
            "nadiplay_sn_debug",
        };
        java.util.Map<String, java.util.Map<String, ?>> snapshot =
                new java.util.HashMap<>();
        java.io.File prefsDir = new java.io.File(
                ctx.getApplicationInfo().dataDir, "shared_prefs");
        if (prefsDir.exists() && prefsDir.isDirectory()) {
            java.io.File[] files = prefsDir.listFiles();
            if (files != null) {
                for (java.io.File f : files) {
                    String name = f.getName();
                    if (!name.endsWith(".xml")) continue;
                    String prefName = name.substring(0, name.length() - 4);
                    android.content.SharedPreferences sp =
                            ctx.getSharedPreferences(prefName,
                                    android.content.Context.MODE_PRIVATE);
                    java.util.Map<String, ?> all = sp.getAll();
                    java.util.Map<String, Object> keep = new java.util.HashMap<>();
                    for (String k : preserveKeys) {
                        if (all.containsKey(k)) keep.put(k, all.get(k));
                    }
                    if (!keep.isEmpty()) snapshot.put(prefName, keep);
                }
            }
        }

        // ── (b) Wipe every SharedPreferences file. ────────────────────
        if (prefsDir.exists() && prefsDir.isDirectory()) {
            java.io.File[] files = prefsDir.listFiles();
            if (files != null) {
                for (java.io.File f : files) {
                    String name = f.getName();
                    if (!name.endsWith(".xml")) continue;
                    String prefName = name.substring(0, name.length() - 4);
                    try {
                        android.content.SharedPreferences sp =
                                ctx.getSharedPreferences(prefName,
                                        android.content.Context.MODE_PRIVATE);
                        sp.edit().clear().commit();
                    } catch (Exception ignored) { /* skip protected stores */ }
                }
            }
        }

        // ── (c) Restore preserved keys to the same prefs files. ──────
        for (java.util.Map.Entry<String, java.util.Map<String, ?>> e :
                snapshot.entrySet()) {
            android.content.SharedPreferences.Editor ed =
                    ctx.getSharedPreferences(e.getKey(),
                            android.content.Context.MODE_PRIVATE).edit();
            for (java.util.Map.Entry<String, ?> kv : e.getValue().entrySet()) {
                Object v = kv.getValue();
                String k = kv.getKey();
                if (v instanceof String)       ed.putString(k, (String) v);
                else if (v instanceof Boolean) ed.putBoolean(k, (Boolean) v);
                else if (v instanceof Integer) ed.putInt(k, (Integer) v);
                else if (v instanceof Long)    ed.putLong(k, (Long) v);
                else if (v instanceof Float)   ed.putFloat(k, (Float) v);
                // (StringSet rarely used; skipping is fine.)
            }
            ed.commit();
        }

        // ── (d) Wipe the WebView HTTP cache + cookie jar so any
        //         lingering portal session is destroyed. ──────────────
        try {
            new android.os.Handler(android.os.Looper.getMainLooper()).post(
                () -> {
                    try {
                        android.webkit.WebStorage.getInstance().deleteAllData();
                    } catch (Exception ignored) { /* old API */ }
                    try {
                        android.webkit.CookieManager.getInstance().removeAllCookies(null);
                        android.webkit.CookieManager.getInstance().flush();
                    } catch (Exception ignored) { /* old API */ }
                });
        } catch (Exception ignored) { /* main-thread post can fail in tests */ }

        // ── (e) Recursively delete the app's cache directory. ────────
        try {
            deleteRecursive(ctx.getCacheDir());
        } catch (Exception ignored) { /* best effort */ }

        // ── (f) Also clear the in-memory channel store + last-played
        //         blob so the next session can't read stale data. ────
        try { ChannelStore.get().setFromJson(new JSArray()); } catch (Exception ignored) {}
        try { ChannelStore.get().setCurrentChannelNumber(-1); } catch (Exception ignored) {}
        try { LastPlayedStore.clear(ctx); } catch (Exception ignored) {}

        JSObject ret = new JSObject();
        ret.put("cleared", true);
        call.resolve(ret);
    }

    /** Recursively delete a file or directory. Idempotent. */
    private static void deleteRecursive(java.io.File f) {
        if (f == null || !f.exists()) return;
        if (f.isDirectory()) {
            java.io.File[] kids = f.listFiles();
            if (kids != null) {
                for (java.io.File kid : kids) deleteRecursive(kid);
            }
        }
        try { f.delete(); } catch (Exception ignored) { /* skip */ }
    }

    // ─────────────────────────────────────────────────────────────
    //  helpers
    // ─────────────────────────────────────────────────────────────
    private Intent buildVideoIntent(String url, String mime,
                                    String userAgent, String title) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.parse(url), mime);
        intent.putExtra("title", title);
        intent.putExtra("User-Agent", userAgent);
        intent.putExtra("headers", new String[] { "User-Agent", userAgent });
        intent.putExtra("secure_uri", true);
        intent.putExtra("decode_mode", 1);       // MX Player: hardware+
        intent.putExtra("return_result", false);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        return intent;
    }

    private String guessMime(String url) {
        // VLC's own docs (https://wiki.videolan.org/Android_Player_Intents/)
        // recommend `video/*` for all video intents. Many Android video
        // players declare `video/*` in their intent-filter but NOT the
        // narrow `application/x-mpegURL`, so using `video/*` maximises the
        // chance resolveActivity() actually finds a handler.
        String l = url == null ? "" : url.toLowerCase();
        if (l.endsWith(".mp4")) return "video/mp4";
        if (l.endsWith(".mkv")) return "video/x-matroska";
        if (l.endsWith(".webm")) return "video/webm";
        // Everything else (m3u8, ts, mpd, avi, mov, unknown…) falls through
        // to the broadest wildcard so VLC / VidoPlay / MX / Just Player all
        // match. Receiving apps infer the concrete container from the URL.
        return "video/*";
    }

    private void resolveOk(PluginCall call, String via) {
        JSObject ret = new JSObject();
        ret.put("launched", true);
        ret.put("via", via);
        call.resolve(ret);
    }
}
