// Bridge to the native ProxyPlugin (see
// android/app/src/main/java/chat/vojo/app/ProxyPlugin.java).
//
// "Bring your own proxy" on Android: the user enters their own HTTP /
// SOCKS5 proxy (host/port + optional user/pass) and Vojo routes its own
// WebView traffic through it via androidx.webkit ProxyController, so the
// homeserver is reachable on networks that block it. No VpnService, no
// Vojo-operated servers.
//
// Secrets (the proxy password) transit JS only at entry — they are written
// straight into a native Tink-encrypted store and NEVER read back, never
// persisted in localStorage / settingsAtom / any JS state. getState
// deliberately omits the password.
//
// Web / Electron have no analogue here (web cannot client-proxy at all; the
// Chromium net stack owns proxy selection). Every call is a no-op off
// Android — mirrors plugins/polling.ts.

import { registerPlugin, type PluginListenerHandle } from '@capacitor/core';
import { isAndroidPlatform } from '../utils/capacitor';

export type ProxyType = 'http' | 'socks5';

// Live status of the override. 'off' = no override applied; 'connecting' =
// applied but not yet confirmed reachable; 'ok' = a homeserver request went
// through; 'error' = the WebView reported a proxy/connection error on the
// homeserver origin (dead / wrong proxy — see ProxyManager.onWebViewError).
export type ProxyStatus = 'off' | 'connecting' | 'ok' | 'error';

// Non-secret view of the stored config. The password is intentionally
// absent — the native side never hands it back.
export type ProxyConfigView = {
  configured: boolean;
  type?: ProxyType;
  host?: string;
  port?: number;
  hasAuth?: boolean;
  username?: string;
};

export type ProxyState = ProxyConfigView & {
  enabled: boolean;
  status: ProxyStatus;
  // WebViewFeature.isFeatureSupported(PROXY_OVERRIDE) — false on ancient
  // WebView APKs (< 72). When false the feature is unavailable and the UI
  // must say so instead of silently doing nothing.
  supported: boolean;
  // PROXY_OVERRIDE_REVERSE_BYPASS — when false we cannot host-scope the
  // override and fall back to routing all WebView traffic through the proxy.
  reverseBypassSupported: boolean;
  // Epoch-ms when the override was applied (0/undefined when off) — uptime base.
  enabledSince?: number;
  // The homeserver host the override is scoped to (undefined = route-all).
  scopeHost?: string;
  // Whether traffic rides the loopback relay (authed proxy) vs direct.
  relayed?: boolean;
  // Client-measured reachability ping (NOT from native): set by the JS probe
  // that fetches `/_matrix/client/versions` through the proxy.
  latencyMs?: number;
  // Epoch-ms of the last reachability probe.
  checkedAt?: number;
  // Epoch-ms of the most recent background native send that was refused because
  // the relay couldn't start (fail-closed). 0/undefined = none. Lets the UI
  // warn that background traffic was blocked (proxy_support.md §15 #3, F17).
  nativeBlockedAt?: number;
};

export type ProxyStatusEvent = { status: ProxyStatus; detail?: string };

interface ProxyPluginIface {
  // Persist creds (Tink). Does NOT enable the override. Password is consumed
  // here and never returned.
  setConfig(opts: {
    type: ProxyType;
    host: string;
    port: number;
    username?: string;
    password?: string;
  }): Promise<void>;
  // Wipe creds + disable the override + tear down the relay.
  clearConfig(): Promise<void>;
  // Apply or remove the override. `homeserverHost` scopes the reverse-bypass
  // (only that host is proxied); omit it pre-login to route everything during
  // discovery. `routeAll` forces full coverage even when a host is known.
  setEnabled(opts: {
    enabled: boolean;
    homeserverHost?: string;
    routeAll?: boolean;
  }): Promise<ProxyState>;
  // Full state for hydrating the UI.
  getState(): Promise<ProxyState>;
  // Re-apply the stored (enabled) override at app boot, before login.
  applyAtBoot(opts: { homeserverHost?: string }): Promise<ProxyState>;
  addListener(
    eventName: 'proxyStatus',
    listenerFunc: (event: ProxyStatusEvent) => void
  ): Promise<PluginListenerHandle>;
}

const DEFAULT_STATE: ProxyState = {
  configured: false,
  enabled: false,
  status: 'off',
  supported: false,
  reverseBypassSupported: false,
};

const noopPlugin: ProxyPluginIface = {
  setConfig: async () => undefined,
  clearConfig: async () => undefined,
  setEnabled: async () => DEFAULT_STATE,
  getState: async () => DEFAULT_STATE,
  applyAtBoot: async () => DEFAULT_STATE,
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  addListener: async (_e, _f) => ({ remove: async () => undefined }),
};

const plugin = registerPlugin<ProxyPluginIface>('Proxy', {
  web: noopPlugin,
});

const guard = async <T>(fn: () => Promise<T>, fallback: T): Promise<T> => {
  if (!isAndroidPlatform()) return fallback;
  try {
    return await fn();
  } catch (err) {
    // Old APK installed before the plugin shipped, or a transient bridge
    // error. Swallow — the proxy is an opt-in connectivity aid, not a hard
    // boot dependency. The UI shows the fallback state (off / unsupported).
    // eslint-disable-next-line no-console
    console.warn('[proxy] native call failed:', err);
    return fallback;
  }
};

export const proxy = {
  // setConfig is the ONE call that must NOT swallow failures: it persists the
  // user's creds, and a silent reject would let the form report success, wipe
  // the typed password, and leave nothing stored (F11). Let it reject so the
  // form can show an error and keep the password. No-op (resolve) off Android.
  setConfig: (opts: {
    type: ProxyType;
    host: string;
    port: number;
    username?: string;
    password?: string;
  }): Promise<void> => {
    if (!isAndroidPlatform()) return Promise.resolve();
    return plugin.setConfig(opts);
  },
  clearConfig: () => guard(() => plugin.clearConfig(), undefined),
  setEnabled: (opts: { enabled: boolean; homeserverHost?: string; routeAll?: boolean }) =>
    guard(() => plugin.setEnabled(opts), DEFAULT_STATE),
  getState: () => guard(() => plugin.getState(), DEFAULT_STATE),
  applyAtBoot: (homeserverHost?: string) =>
    guard(() => plugin.applyAtBoot({ homeserverHost }), DEFAULT_STATE),
  addListener: (listener: (event: ProxyStatusEvent) => void): Promise<PluginListenerHandle> => {
    if (!isAndroidPlatform()) {
      return Promise.resolve({ remove: async () => undefined });
    }
    return plugin.addListener('proxyStatus', listener);
  },
};
