import { useAtom } from 'jotai';
import { useCallback, useEffect } from 'react';
import { proxy, type ProxyState, type ProxyType } from '../plugins/proxy';
import { proxyStateAtom } from '../state/proxy';

// Derive the bare apex host the override should be scoped to from a homeserver
// base URL (`https://matrix.vojo.chat` → `matrix.vojo.chat`). Returns
// undefined for an empty / unparseable URL so the caller falls back to
// route-all (pre-login discovery). Kept here so both the settings page and the
// pre-login entry scope identically.
export const hostFromBaseUrl = (baseUrl?: string): string | undefined => {
  if (!baseUrl) return undefined;
  try {
    return new URL(baseUrl).hostname || undefined;
  } catch {
    return undefined;
  }
};

export type SaveProxyInput = {
  type: ProxyType;
  host: string;
  port: number;
  username?: string;
  password?: string;
};

export type UseProxyResult = {
  state: ProxyState;
  refresh: () => Promise<void>;
  // Persist creds (does not enable). Returns the refreshed state.
  saveConfig: (input: SaveProxyInput) => Promise<ProxyState>;
  // Apply / remove the override, scoping to `homeserverBaseUrl` when given.
  setEnabled: (
    enabled: boolean,
    homeserverBaseUrl?: string,
    routeAll?: boolean
  ) => Promise<ProxyState>;
  // Wipe creds + disable + tear down the relay.
  clearConfig: () => Promise<ProxyState>;
  // Re-run the reachability + latency probe against the homeserver (the
  // "Check"/refresh action in the status card).
  probe: (homeserverBaseUrl: string) => Promise<void>;
};

export function useProxy(): UseProxyResult {
  const [state, setState] = useAtom(proxyStateAtom);

  const refresh = useCallback(async () => {
    const next = await proxy.getState();
    setState((prev) => {
      // Reachability ('ok'/'error') is owned by the JS probe — native can only
      // ever report 'off' / 'connecting' / a latched WebView 'error'. On a
      // getState hydrate, keep the probe's verdict while the proxy is enabled so
      // a STALE native field can't regress it: neither native 'connecting'
      // dropping a confirmed 'ok' (F3), nor a latched native 'error' re-sticking
      // after a recheck already healed it to 'ok'. Only 'off' (disabled) wins,
      // since that's authoritative. Live failures still arrive via the listener.
      const keepProbeVerdict = next.enabled && (prev.status === 'ok' || prev.status === 'error');
      return {
        ...next,
        // latencyMs / checkedAt are client-only probe results — getState
        // (native) never reports them. Preserve across a refresh (e.g. another
        // consumer like DirectSelfRow mounting) instead of wiping the ping the
        // user just measured (F3); drop only when disabled.
        latencyMs: next.enabled ? prev.latencyMs : undefined,
        checkedAt: next.enabled ? prev.checkedAt : undefined,
        status: keepProbeVerdict ? prev.status : next.status,
      };
    });
  }, [setState]);

  // Hydrate once on mount, then keep `status` live via the native listener
  // (dead-proxy detection fires a 'proxyStatus' event from the WebView error
  // callbacks — see ProxyManager.onWebViewError).
  useEffect(() => {
    let active = true;
    refresh();
    const handlePromise = proxy.addListener((event) => {
      if (!active) return;
      setState((prev) => {
        // Native re-emits 'connecting' on every (re)apply; once the JS probe
        // has a verdict, a late 'connecting' event must not regress it (F4).
        // 'error' (dead-proxy detected) and 'off' (disabled) are authoritative
        // and always win.
        if (event.status === 'connecting' && (prev.status === 'ok' || prev.status === 'error')) {
          return prev;
        }
        return { ...prev, status: event.status };
      });
    });
    return () => {
      active = false;
      handlePromise.then((h) => h.remove()).catch(() => undefined);
    };
  }, [refresh, setState]);

  const saveConfig = useCallback(
    async (input: SaveProxyInput) => {
      await proxy.setConfig(input);
      const next = await proxy.getState();
      setState(next);
      return next;
    },
    [setState]
  );

  // Confirm reachability through the (now-applied) proxy AND measure latency by
  // fetching `/_matrix/client/versions` (a tiny unauthenticated CS-API endpoint)
  // through the WebView, which rides the override. This is the authoritative
  // "the proxy actually reaches the homeserver" + "ping" signal (F5 dead-proxy
  // detection) — the native 'connecting'/'error' callbacks never flip to 'ok'.
  const probe = useCallback(
    async (homeserverBaseUrl: string) => {
      setState((prev) => ({ ...prev, status: 'connecting' }));
      const controller = new AbortController();
      const timer = setTimeout(() => controller.abort(), 8000);
      const t0 = performance.now();
      try {
        const res = await fetch(`${homeserverBaseUrl.replace(/\/$/, '')}/_matrix/client/versions`, {
          signal: controller.signal,
          cache: 'no-store',
        });
        const latencyMs = Math.round(performance.now() - t0);
        setState((prev) => ({
          ...prev,
          status: res.ok ? 'ok' : 'error',
          latencyMs: res.ok ? latencyMs : undefined,
          checkedAt: Date.now(),
        }));
      } catch {
        setState((prev) => ({
          ...prev,
          status: 'error',
          latencyMs: undefined,
          checkedAt: Date.now(),
        }));
      } finally {
        clearTimeout(timer);
      }
    },
    [setState]
  );

  const setEnabled = useCallback(
    async (enabled: boolean, homeserverBaseUrl?: string, routeAll?: boolean) => {
      const next = await proxy.setEnabled({
        enabled,
        homeserverHost: hostFromBaseUrl(homeserverBaseUrl),
        routeAll,
      });
      setState(next);
      // Skipped pre-login (no base URL → route-all); there the login flow is
      // the real signal.
      if (enabled && next.enabled && homeserverBaseUrl) {
        await probe(homeserverBaseUrl);
      }
      return next;
    },
    [setState, probe]
  );

  const clearConfig = useCallback(async () => {
    await proxy.clearConfig();
    const next = await proxy.getState();
    setState(next);
    return next;
  }, [setState]);

  return { state, refresh, saveConfig, setEnabled, clearConfig, probe };
}
