import { create } from 'zustand';
import { supabase } from '../lib/supabase';

export type LogEntry = {
  id: number;
  ts: number;
  text: string;
};

type RealtimeDebugState = {
  connectionState: string;
  reconnectCount: number;
  lastEventAt: number | null;
  tokenExpiresAt: number | null;
  log: LogEntry[];
  initialized: boolean;

  appendLog: (text: string) => void;
  clearLog: () => void;
  refreshSnapshot: () => Promise<void>;
  init: () => void;
};

let logIdSeq = 0;

export const useRealtimeDebugStore = create<RealtimeDebugState>((set, get) => ({
  connectionState: 'unknown',
  reconnectCount: 0,
  lastEventAt: null,
  tokenExpiresAt: null,
  log: [],
  initialized: false,

  appendLog(text) {
    const entry: LogEntry = { id: logIdSeq++, ts: Date.now(), text };
    set((s) => ({
      log: [entry, ...s.log].slice(0, 100),
      lastEventAt: Date.now(),
    }));
  },

  clearLog() {
    set({ log: [] });
  },

  async refreshSnapshot() {
    const rt = supabase.realtime as any;

    // connectionState — Supabase JS v2 exposes this on the internal conn object
    let connState = 'unknown';
    try {
      // supabase.realtime.connectionState() exists in @supabase/realtime-js >= 2.x
      if (typeof rt.connectionState === 'function') {
        connState = String(rt.connectionState());
      } else if (rt.conn?.connectionState) {
        connState = String(rt.conn.connectionState());
      }
    } catch {
      connState = 'error';
    }

    const { data } = await supabase.auth.getSession();
    const tokenExpiresAt = data.session?.expires_at ?? null;

    set({ connectionState: connState, tokenExpiresAt: tokenExpiresAt ?? null });
  },

  init() {
    if (get().initialized) return;
    set({ initialized: true });

    const rt = supabase.realtime as any;

    // Patch socket-level open/close/error — supabase-js internal conn is a Phoenix socket
    function hookSocket() {
      const conn = rt.conn;
      if (!conn) return false;

      const origOnOpen = conn.onOpen?.bind(conn);
      const origOnClose = conn.onClose?.bind(conn);
      const origOnError = conn.onError?.bind(conn);

      conn.onOpen(() => {
        get().appendLog('socket:open');
        set({ connectionState: 'OPEN' });
      });

      conn.onClose((event: { code?: number; reason?: string }) => {
        const reason = event?.reason ? `  reason=${event.reason}` : '';
        get().appendLog(`socket:close  code=${event?.code ?? '?'}${reason}`);
        set((s) => ({
          connectionState: 'CLOSED',
          reconnectCount: s.reconnectCount + 1,
        }));
      });

      conn.onError((error: unknown) => {
        get().appendLog(`socket:error  ${String(error)}`);
      });

      // Reconnect attempts — Phoenix socket emits reconnectAttempts counter change
      // We monkey-patch reconnectTimer to detect attempts
      const origReconnect = conn.reconnect?.bind(conn);
      if (typeof origReconnect === 'function') {
        conn.reconnect = function (...args: unknown[]) {
          set((s) => ({ reconnectCount: s.reconnectCount + 1 }));
          get().appendLog(`socket:reconnect-attempt #${get().reconnectCount}`);
          return origReconnect(...args);
        };
      }

      return true;
    }

    // The socket may not yet exist when init() is called (before first subscription).
    // Poll briefly then give up — subsequent channel subscriptions will still log via channel listener.
    let attempts = 0;
    const socketPollInterval = setInterval(() => {
      attempts++;
      if (hookSocket() || attempts > 20) {
        clearInterval(socketPollInterval);
      }
    }, 500);

    // Listen to all channel state changes via supabase.realtime internal channels array
    // We wrap channel.subscribe to intercept status callbacks
    // Approach: patch RealtimeChannel.prototype.subscribe so any new channel gets logged
    try {
      const RealtimeChannelProto = Object.getPrototypeOf(supabase.channel('__probe__'));
      supabase.removeAllChannels();

      const origChanSubscribe = RealtimeChannelProto.subscribe;
      if (typeof origChanSubscribe === 'function' && !RealtimeChannelProto.__debugPatched) {
        RealtimeChannelProto.__debugPatched = true;
        RealtimeChannelProto.subscribe = function (
          callback?: (status: string, err?: unknown) => void,
          timeout?: number,
        ) {
          const channelTopic: string = this.topic ?? '?';
          return origChanSubscribe.call(
            this,
            (status: string, err?: unknown) => {
              get().appendLog(
                `channel:${status.toLowerCase()}  ${channelTopic}${err ? `  ${String(err)}` : ''}`,
              );
              if (callback) callback(status, err);
            },
            timeout,
          );
        };
      }
    } catch {
      // Prototype patching failed — not critical, socket-level hooks still work
    }

    // Initial snapshot
    get().refreshSnapshot();
  },
}));
