package chat.vojo.app;

import android.util.Log;

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

import chat.vojo.app.ProxyConfigStore.ProxyCreds;

/**
 * JS ↔ Android bridge for the "bring your own proxy" feature
 * (proxy_android_impl.md §3). Mirrors PollingPlugin's shape.
 *
 * The password transits JS exactly once at entry ({@link #setConfig}) and is
 * written straight into the Tink-encrypted store; it is NEVER returned to JS
 * (getConfig / getState omit it) and never persisted in localStorage / JS state.
 *
 * Capacitor dispatches plugin methods off the main thread, so the blocking
 * socket / setProxyOverride work in apply()/disable() is safe here.
 */
@CapacitorPlugin(name = "Proxy")
public class ProxyPlugin extends Plugin {

    private static final String TAG = "ProxyPlugin";

    @Override
    public void load() {
        ProxyManager pm = ProxyManager.get(getContext());
        pm.setStatusListener((status, detail) -> {
            JSObject o = new JSObject();
            o.put("status", status);
            if (detail != null) o.put("detail", detail);
            notifyListeners("proxyStatus", o);
        });

        // Attach our WebViewClient subclass so onReceivedHttpAuthRequest can
        // answer the proxy 407 and onReceivedError can surface a dead proxy.
        // Must touch the WebView on the UI thread.
        final Bridge bridge = getBridge();
        try {
            getActivity().runOnUiThread(() -> {
                try {
                    // bridge.setWebViewClient (not webView.setWebViewClient) so
                    // Capacitor tracks it as THE client and won't silently
                    // overwrite our proxy-auth hook later.
                    bridge.setWebViewClient(new ProxyBridgeWebViewClient(bridge));
                } catch (Throwable t) {
                    Log.w(TAG, "failed to attach proxy WebViewClient");
                }
            });
        } catch (Throwable t) {
            Log.w(TAG, "runOnUiThread unavailable");
        }
    }

    @PluginMethod
    public void setConfig(PluginCall call) {
        String type = call.getString("type");
        String host = call.getString("host");
        Integer port = call.getInt("port");
        if (!"http".equals(type) && !"socks5".equals(type)) {
            call.reject("invalid_type");
            return;
        }
        if (host == null || host.isEmpty() || host.contains("://") || host.contains(" ")) {
            call.reject("invalid_host");
            return;
        }
        if (port == null || port < 1 || port > 65535) {
            call.reject("invalid_port");
            return;
        }

        ProxyManager pm = ProxyManager.get(getContext());
        String username = call.getString("username");
        boolean hasUser = username != null && !username.isEmpty();
        String password = call.getString("password");

        String finalUsername = hasUser ? username : null;
        String finalPassword = null;
        if (hasUser) {
            if (password != null && !password.isEmpty()) {
                finalPassword = password;
            } else {
                // No new password supplied — keep the existing one (lets the
                // user edit host/port without re-typing the secret), but ONLY
                // when the username is unchanged: reusing the old password for a
                // NEW username would silently send user-B the password of
                // user-A (F8). A changed username with no password = no-auth.
                ProxyCreds existing = pm.store().load();
                finalPassword = (existing != null
                        && existing.password != null
                        && username.equals(existing.username)) ? existing.password : "";
            }
        }

        boolean ok = pm.store().save(new ProxyCreds(type, host, port, finalUsername, finalPassword));
        if (!ok) {
            call.reject("store_failed");
            return;
        }
        call.resolve();
    }

    @PluginMethod
    public void clearConfig(PluginCall call) {
        ProxyManager pm = ProxyManager.get(getContext());
        pm.disable();
        pm.store().wipe();
        call.resolve();
    }

    @PluginMethod
    public void setEnabled(PluginCall call) {
        boolean enabled = Boolean.TRUE.equals(call.getBoolean("enabled", false));
        String host = call.getString("homeserverHost");
        boolean routeAll = Boolean.TRUE.equals(call.getBoolean("routeAll", false));

        ProxyManager pm = ProxyManager.get(getContext());
        if (enabled) {
            ProxyCreds creds = pm.store().load();
            if (creds != null) {
                boolean effectiveRouteAll = routeAll || host == null || host.isEmpty();
                boolean ok = pm.apply(creds, host, effectiveRouteAll);
                pm.store().setEnabled(ok);
            }
        } else {
            pm.disable();
            pm.store().setEnabled(false);
        }
        call.resolve(buildState(pm));
    }

    @PluginMethod
    public void getState(PluginCall call) {
        call.resolve(buildState(ProxyManager.get(getContext())));
    }

    @PluginMethod
    public void applyAtBoot(PluginCall call) {
        ProxyManager pm = ProxyManager.get(getContext());
        pm.applyAtBoot(call.getString("homeserverHost"));
        call.resolve(buildState(pm));
    }

    private JSObject buildState(ProxyManager pm) {
        JSObject o = new JSObject();
        o.put("enabled", pm.isEnabled());
        o.put("status", pm.getStatus());
        o.put("supported", ProxyManager.isOverrideSupported());
        o.put("reverseBypassSupported", ProxyManager.isReverseBypassSupported());
        o.put("enabledSince", pm.getEnabledSince());
        o.put("relayed", pm.isRelayed());
        o.put("nativeBlockedAt", pm.getNativeBlockedAt());
        if (pm.getScopeHost() != null) o.put("scopeHost", pm.getScopeHost());
        ProxyCreds c = pm.store().load();
        if (c == null) {
            o.put("configured", false);
        } else {
            o.put("configured", true);
            o.put("type", c.type);
            o.put("host", c.host);
            o.put("port", c.port);
            o.put("hasAuth", c.hasAuth());
            if (c.username != null) o.put("username", c.username);
        }
        return o;
    }
}
