// Browser-side strategy engine. Polls Delta public candles for a small
// universe of top-liquidity perpetual contracts and produces pending
// TradeSignal objects using multi-indicator confluence. Used as a graceful
// fallback so the Pending Signals panel is never empty in preview / demos,
// even when the Node signal engine is offline.

import { useEffect, useMemo, useState } from "react";
import type { TradeSignal } from "@/lib/backend-client";
import { atr, bollinger, ema, macd, rsi } from "@/lib/indicators-lite";
import { getCustomAlgos, getEnabledMap } from "@/lib/algo-registry";
import { evaluateCustom, type IndicatorSnapshot } from "@/lib/custom-algo-eval";
import { getAllowedCoins } from "@/lib/user-prefs";

const UNIVERSE = ["BTCUSD", "ETHUSD", "SOLUSD", "XRPUSD", "DOGEUSD", "BNBUSD", "AVAXUSD", "LINKUSD"];
const TIMEFRAMES: { tf: string; deltaRes: string; bucketSec: number }[] = [
  { tf: "15m", deltaRes: "15m", bucketSec: 900 },
  { tf: "1h", deltaRes: "1h", bucketSec: 3600 },
];
const BASES = ["https://api.india.delta.exchange", "https://api.delta.exchange"];
const POLL_MS = 20_000;

type Bar = { time: number; open: number; high: number; low: number; close: number; volume: number };

async function fetchCandles(symbol: string, resolution: string, bucketSec: number): Promise<Bar[]> {
  const end = Math.floor(Date.now() / 1000);
  const start = end - bucketSec * 300;
  const qs = `resolution=${resolution}&symbol=${encodeURIComponent(symbol)}&start=${start}&end=${end}`;
  for (const base of BASES) {
    try {
      const r = await fetch(`${base}/v2/history/candles?${qs}`, { cache: "no-store" });
      if (!r.ok) continue;
      const j = await r.json();
      const rows: any[] = Array.isArray(j?.result) ? j.result : [];
      if (!rows.length) continue;
      return rows
        .map((x) => ({
          time: Number(x.time),
          open: Number(x.open),
          high: Number(x.high),
          low: Number(x.low),
          close: Number(x.close),
          volume: Number(x.volume) || 0,
        }))
        .filter((b) => Number.isFinite(b.close))
        .sort((a, b) => a.time - b.time);
    } catch { /* try next */ }
  }
  return [];
}

interface Setup {
  algo: string;
  algo_name: string;
  side: "LONG" | "SHORT";
  confidence: number; // 0..1
  reasons: string[];
  ind: { rsi?: number; macd?: string; ema?: string };
}

function evaluate(candles: Bar[], tf: string): Setup[] {
  if (candles.length < 60) return [];
  const closes = candles.map((c) => c.close);
  const last = closes.length - 1;
  const price = closes[last];

  const rsiArr = rsi(closes, 14);
  const emaFast = ema(closes, 20);
  const emaSlow = ema(closes, 50);
  const m = macd(closes, 12, 26, 9);
  const bb = bollinger(closes, 20, 2);

  const r = rsiArr[last];
  const ef = emaFast[last], es = emaSlow[last];
  const efP = emaFast[last - 1], esP = emaSlow[last - 1];
  const mh = m.hist[last], mhP = m.hist[last - 1];
  const bbU = bb.upper[last], bbL = bb.lower[last];

  const enabled = getEnabledMap();
  const isOn = (k: string) => enabled[k] !== false;

  const setups: Setup[] = [];

  // 1) EMA Cross
  if (isOn("ema_cross") && Number.isFinite(ef) && Number.isFinite(es) && Number.isFinite(efP) && Number.isFinite(esP)) {
    if (efP <= esP && ef > es) {
      setups.push({
        algo: "ema_cross", algo_name: "EMA Cross", side: "LONG",
        confidence: 0.62, reasons: ["EMA20 crossed above EMA50"],
        ind: { ema: `EMA20>${ef.toFixed(2)} > EMA50 ${es.toFixed(2)}` },
      });
    } else if (efP >= esP && ef < es) {
      setups.push({
        algo: "ema_cross", algo_name: "EMA Cross", side: "SHORT",
        confidence: 0.62, reasons: ["EMA20 crossed below EMA50"],
        ind: { ema: `EMA20 ${ef.toFixed(2)} < EMA50 ${es.toFixed(2)}` },
      });
    }
  }

  // 2) RSI Reversal
  if (isOn("rsi_reversal") && Number.isFinite(r)) {
    if (r < 30) setups.push({ algo: "rsi_reversal", algo_name: "RSI Reversal", side: "LONG",
      confidence: 0.58 + (30 - r) / 100, reasons: [`RSI oversold at ${r.toFixed(1)}`], ind: { rsi: r } });
    else if (r > 70) setups.push({ algo: "rsi_reversal", algo_name: "RSI Reversal", side: "SHORT",
      confidence: 0.58 + (r - 70) / 100, reasons: [`RSI overbought at ${r.toFixed(1)}`], ind: { rsi: r } });
  }

  // 3) MACD Momentum
  if (isOn("macd_momentum") && Number.isFinite(mh) && Number.isFinite(mhP)) {
    if (mhP <= 0 && mh > 0) setups.push({ algo: "macd_momentum", algo_name: "MACD Momentum", side: "LONG",
      confidence: 0.6, reasons: ["MACD histogram flipped positive"], ind: { macd: "bull cross" } });
    else if (mhP >= 0 && mh < 0) setups.push({ algo: "macd_momentum", algo_name: "MACD Momentum", side: "SHORT",
      confidence: 0.6, reasons: ["MACD histogram flipped negative"], ind: { macd: "bear cross" } });
  }

  // 4) Bollinger Reversion
  if (isOn("bb_reversion") && Number.isFinite(bbU) && Number.isFinite(bbL)) {
    if (price <= bbL) setups.push({ algo: "bb_reversion", algo_name: "BB Reversion", side: "LONG",
      confidence: 0.57, reasons: ["Price tagged lower Bollinger band"], ind: {} });
    else if (price >= bbU) setups.push({ algo: "bb_reversion", algo_name: "BB Reversion", side: "SHORT",
      confidence: 0.57, reasons: ["Price tagged upper Bollinger band"], ind: {} });
  }

  // 5) Confluence — bull/bear when multiple agree
  if (isOn("confluence")) {
    const bull = (ef > es ? 1 : 0) + (r < 45 ? 1 : 0) + (mh > 0 ? 1 : 0);
    const bear = (ef < es ? 1 : 0) + (r > 55 ? 1 : 0) + (mh < 0 ? 1 : 0);
    if (bull >= 2) setups.push({ algo: "confluence", algo_name: "Confluence", side: "LONG",
      confidence: 0.6 + bull * 0.06, reasons: ["Trend + momentum + RSI aligned bullish"],
      ind: { rsi: r, macd: mh > 0 ? "bull" : "neutral", ema: ef > es ? "up" : "flat" } });
    if (bear >= 2) setups.push({ algo: "confluence", algo_name: "Confluence", side: "SHORT",
      confidence: 0.6 + bear * 0.06, reasons: ["Trend + momentum + RSI aligned bearish"],
      ind: { rsi: r, macd: mh < 0 ? "bear" : "neutral", ema: ef < es ? "down" : "flat" } });
  }

  // 6) Custom user-defined algos (AI-generated rule specs)
  const snap: IndicatorSnapshot = {
    rsi: Number.isFinite(r) ? r : NaN,
    ema_fast_gt_slow: Number.isFinite(ef) && Number.isFinite(es) ? (ef > es ? 1 : 0) : NaN,
    macd_hist: Number.isFinite(mh) ? mh : NaN,
    price_vs_bb_upper: Number.isFinite(bbU) ? (price >= bbU ? 1 : 0) : NaN,
    price_vs_bb_lower: Number.isFinite(bbL) ? (price <= bbL ? 1 : 0) : NaN,
  };
  for (const c of getCustomAlgos()) {
    if (!isOn(c.key)) continue;
    if (c.timeframe !== "any" && c.timeframe !== tf) continue;
    const hit = evaluateCustom(c, snap);
    if (hit) {
      setups.push({
        algo: c.key,
        algo_name: c.label,
        side: hit.side,
        confidence: hit.confidence,
        reasons: hit.reasons.length ? hit.reasons : [c.description ?? "Custom rule matched"],
        ind: { rsi: snap.rsi, macd: snap.macd_hist > 0 ? "bull" : "bear", ema: snap.ema_fast_gt_slow ? "up" : "down" },
      });
    }
  }

  return setups;
}


function toSignal(symbol: string, tf: string, setup: Setup, candles: Bar[]): TradeSignal {
  const price = candles[candles.length - 1].close;
  const a = atr(candles, 14);
  const atrVal = Number.isFinite(a[a.length - 1]) ? a[a.length - 1] : price * 0.01;
  const dir = setup.side === "LONG" ? 1 : -1;
  const sl = price - dir * atrVal * 1.5;
  const tp = price + dir * atrVal * 2.5;
  const now = new Date();
  return {
    _id: `local-${symbol}-${tf}-${setup.algo}-${setup.side}-${Math.floor(now.getTime() / 60000)}`,
    coin_symbol: symbol,
    position_side: setup.side,
    entry_price: Number(price.toFixed(price < 1 ? 5 : 2)),
    stop_loss: Number(sl.toFixed(price < 1 ? 5 : 2)),
    take_profit: Number(tp.toFixed(price < 1 ? 5 : 2)),
    order_sizing_type: "PERCENTAGE",
    order_size_value: 2,
    leverage: 5,
    confidence: Math.min(0.95, Math.max(0.4, setup.confidence)),
    rationale: setup.reasons.join(" · "),
    indicators: setup.ind,
    created_at: now.toISOString(),
    expires_at: new Date(now.getTime() + 4 * 60 * 60_000).toISOString(),
    algo: setup.algo,
    algo_name: setup.algo_name,
    timeframe: tf,
  };
}

/**
 * Returns pending signals generated in-browser from live Delta candles.
 * `enabled` should typically be `backendSignals.length === 0` so we only kick
 * in when the Node engine isn't feeding data.
 */
export function useClientSignals(enabled: boolean) {
  const [signals, setSignals] = useState<TradeSignal[]>([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!enabled) { setSignals([]); return; }
    let alive = true;
    let scanId = 0;

    async function scan() {
      const myScan = ++scanId;
      setLoading(true);
      // Accumulate results across symbols/timeframes, keyed by
      // (symbol,side,algo,tf) so each new evaluation replaces its own slot
      // without wiping the rest. This lets signals stream into the UI as
      // each symbol resolves, instead of flipping the whole list at once
      // every POLL_MS.
      const map = new Map<string, TradeSignal>();

      const allowed = getAllowedCoins();
      const universe: string[] = allowed.length
        ? Array.from(new Set([...UNIVERSE.filter((s) => allowed.includes(s)), ...allowed]))
        : [...UNIVERSE];

      for (const symbol of universe) {
        if (!alive || myScan !== scanId) return;
        for (const { tf, deltaRes, bucketSec } of TIMEFRAMES) {
          const candles = await fetchCandles(symbol, deltaRes, bucketSec);
          if (!candles.length) continue;
          for (const setup of evaluate(candles, tf)) {
            if (setup.confidence < 0.55) continue;
            const sig = toSignal(symbol, tf, setup, candles);
            const k = `${sig.coin_symbol}|${sig.position_side}|${sig.algo}|${sig.timeframe}`;
            const prev = map.get(k);
            if (!prev || (sig.confidence ?? 0) > (prev.confidence ?? 0)) map.set(k, sig);
          }
        }
        // Push a snapshot after each symbol so the panel updates live.
        if (alive && myScan === scanId) {
          const merged = Array.from(map.values()).sort(
            (a, b) => (b.confidence ?? 0) - (a.confidence ?? 0),
          );
          setSignals(merged);
        }
      }
      if (alive && myScan === scanId) setLoading(false);
    }

    scan();
    const t = setInterval(scan, POLL_MS);
    return () => { alive = false; clearInterval(t); };
  }, [enabled]);

  return useMemo(() => ({ signals, loading }), [signals, loading]);
}
