import { useEffect, useRef, useState } from "react";
import {
  backendApi,
  getBackendWs,
  type BackendEvent,
  type DeltaPosition,
  type TradeSignal,
  type DeltaAccount,
  type OrderStatus,
} from "@/lib/backend-client";

interface BackendState {
  state: "idle" | "connecting" | "open" | "reconnecting" | "closed";
  attempt: number;
}

// The backend adapters (Mongo / MySQL) return rows with an `id` field, but
// the frontend types and components consistently use `_id`. Normalize once
// at the ingress boundary so `s._id` / `p._id` are always populated no
// matter which driver is behind the API. Without this, approve/reject calls
// call `undefined.startsWith` and crash with "cannot read properties of
// undefined".
type WithMaybeId<T> = T & { id?: string; _id?: string };
function normalizeId<T extends { _id?: string }>(row: WithMaybeId<T>): T {
  if (!row) return row as T;
  if (row._id) return row as T;
  if (row.id) return { ...row, _id: row.id } as T;
  return row as T;
}


/**
 * Subscribes to the backend private stream and keeps positions, pending
 * signals, and account details in sync via WS events, seeded from REST on
 * mount and re-seeded automatically whenever the WebSocket transitions back
 * to "open" after a disconnect (state recovery).
 */
export function useBackendStream() {
  const [positions, setPositions] = useState<DeltaPosition[]>([]);
  const [signals, setSignals] = useState<TradeSignal[]>([]);
  const [account, setAccount] = useState<DeltaAccount | null>(null);
  const [connection, setConnection] = useState<BackendState>({ state: "idle", attempt: 0 });
  const [seedError, setSeedError] = useState<string | null>(null);
  const hasOpenedRef = useRef(false);
  const cancelledRef = useRef(false);

  useEffect(() => {
    cancelledRef.current = false;

    const seed = () => {
      Promise.allSettled([
        backendApi.listPositions(),
        backendApi.listSignals(),
        backendApi.testDeltaCredentials(),
      ])
        .then(([p, s, a]) => {
          if (cancelledRef.current) return;
          if (p.status === "fulfilled") setPositions(p.value.map((row) => normalizeId<DeltaPosition>(row)));
          if (s.status === "fulfilled") setSignals(s.value.map((row) => normalizeId<TradeSignal>(row)));
          if (a.status === "fulfilled") setAccount(a.value);
          const anyErr = [p, s, a].find((r) => r.status === "rejected") as
            | PromiseRejectedResult
            | undefined;
          if (anyErr) setSeedError(String(anyErr.reason));
          else setSeedError(null);
        })
        .catch((e) => setSeedError(String(e)));
    };


    // Initial REST seed — best-effort so the UI keeps working when the
    // backend is not reachable yet. The WS will hydrate later once online.
    seed();

    const ws = getBackendWs();
    const offState = ws.onState((s) => {
      setConnection(s);
      // Automatic state recovery: whenever the socket transitions back to
      // "open" after having previously opened, re-fetch positions/signals/
      // account so the UI reflects the current truth even if events were
      // missed during the outage.
      if (s.state === "open") {
        if (hasOpenedRef.current) seed();
        hasOpenedRef.current = true;
      }
    });
    const offEvt = ws.onEvent((e: BackendEvent) => {
      switch (e.type) {
        case "position.update": {
          const incoming = normalizeId<DeltaPosition>(e.position);
          setPositions((prev) => {
            const idx = prev.findIndex((p) => p._id === incoming._id);
            if (idx === -1) return [...prev, incoming];
            const next = prev.slice();
            next[idx] = { ...prev[idx], ...incoming };
            return next;
          });
          break;
        }
        case "position.close":
          setPositions((prev) => prev.filter((p) => p._id !== e.position_id));
          break;
        case "order.update":
          setPositions((prev) =>
            prev.map((p) =>
              p._id === e.position_id
                ? {
                    ...p,
                    order_status: e.status,
                    fill_price: e.fill_price ?? p.fill_price,
                    filled_size: e.filled_size ?? p.filled_size,
                  }
                : p,
            ),
          );
          break;
        case "signal.new": {
          const incoming = normalizeId<TradeSignal>(e.signal);
          setSignals((prev) =>
            prev.some((s) => s._id === incoming._id) ? prev : [incoming, ...prev],
          );
          break;
        }
        case "signal.expired":
          setSignals((prev) => prev.filter((s) => s._id !== e.signal_id));
          break;
        case "account.update":
          setAccount(e.account);
          break;
      }
    });

    ws.start();
    return () => {
      cancelledRef.current = true;
      offState();
      offEvt();
    };
  }, []);

  return { positions, setPositions, signals, setSignals, account, setAccount, connection, seedError };
}

export type { OrderStatus, DeltaPosition, TradeSignal, DeltaAccount };
