import { useEffect, useRef } from "react";
import { toast } from "sonner";
import { backendApi, type DeltaPosition, type TradeSignal } from "@/lib/backend-client";
import { getApprovalMode } from "@/lib/trade-approval";
import { getAutoExit } from "@/lib/auto-exit";
import { appendAutoExitLog } from "@/lib/auto-exit-log";

/**
 * Global background runner. When Trade Execution Mode = "auto" and the user's
 * auto-exit rules are enabled, this loop:
 *
 *  1. Polls open positions and pending signals every few seconds.
 *  2. Closes any position whose direction is contradicted by a fresh
 *     opposite-side signal above the confidence threshold (trend reversal).
 *  3. Maintains an in-memory high-water-mark of the mark price for each
 *     position and ratchets the server-side stop loss upward (long) or
 *     downward (short) to stay `trailingDistancePct` behind it — never
 *     loosening, only tightening in favour of the trade.
 *
 * Runs invisibly from the app shell so it keeps working on every route.
 */
const POLL_MS = 5_000;

type HwmMap = Record<string, { peak: number; slPushed?: number }>;

function directionSign(side: "LONG" | "SHORT") {
  return side === "LONG" ? 1 : -1;
}

async function evaluate(hwm: HwmMap, notified: Set<string>) {
  if (getApprovalMode() !== "auto") return;
  const cfg = getAutoExit();
  if (!cfg.reverseExit && !cfg.trailingSl) return;

  let positions: DeltaPosition[] = [];
  let signals: TradeSignal[] = [];
  try {
    [positions, signals] = await Promise.all([
      backendApi.listPositions(),
      backendApi.listSignals(),
    ]);
  } catch {
    return; // backend offline — try again next tick
  }

  const openIds = new Set(positions.map((p) => p._id));
  for (const k of Object.keys(hwm)) if (!openIds.has(k)) delete hwm[k];

  for (const p of positions) {
    // Only manage filled/live positions
    if (p.order_status && p.order_status !== "FILLED" && p.order_status !== "PARTIALLY_FILLED") continue;
    const mark = p.current_price || p.entry_price;
    if (!mark || !p.entry_price) continue;

    // 1) Trend-reversal auto-close
    if (cfg.reverseExit) {
      const opposing = signals.find(
        (s) =>
          s.coin_symbol === p.coin_symbol &&
          s.position_side !== p.position_side &&
          (s.confidence ?? 0) >= cfg.reverseMinConfidence,
      );
      if (opposing) {
        const key = `reverse:${p._id}:${opposing._id}`;
        if (!notified.has(key)) {
          notified.add(key);
          const baseLog = {
            symbol: p.coin_symbol,
            positionId: p._id,
            positionSide: p.position_side,
            entryPrice: p.entry_price,
            markPrice: mark,
            signalId: opposing._id,
            signalSide: opposing.position_side,
            signalConfidence: opposing.confidence,
            reverseMinConfidence: cfg.reverseMinConfidence,
          } as const;
          try {
            const res = await backendApi.closePosition(p._id);
            appendAutoExitLog({
              ...baseLog,
              kind: "reversal_close",
              orderStatus: res?.ok ? "submitted" : "unknown",
              note: `Opposite ${opposing.position_side} signal @ ${(opposing.confidence * 100).toFixed(0)}% ≥ ${(cfg.reverseMinConfidence * 100).toFixed(0)}% threshold`,
            });
            toast.warning(`Auto-exit ${p.coin_symbol}`, {
              description: `Trend reversed (${opposing.position_side} @ ${(opposing.confidence * 100).toFixed(0)}%). Position closed.`,
            });
          } catch (e) {
            const msg = e instanceof Error ? e.message : "close request failed";
            appendAutoExitLog({
              ...baseLog,
              kind: "reversal_close_failed",
              error: msg,
            });
            toast.error(`Auto-exit failed for ${p.coin_symbol}`, {
              description: msg,
            });
          }
          continue;
        }
      }
    }

    // 2) Trailing stop-loss ratchet
    if (cfg.trailingSl) {
      const sign = directionSign(p.position_side);
      const state = hwm[p._id] ?? { peak: mark, slPushed: p.stop_loss };
      const isBetter = sign > 0 ? mark > state.peak : mark < state.peak;
      if (isBetter) state.peak = mark;

      const profitPct = ((mark - p.entry_price) / p.entry_price) * 100 * sign;
      if (profitPct >= cfg.trailingActivatePct) {
        const distance = p.entry_price * (cfg.trailingDistancePct / 100);
        const proposedSl = state.peak - sign * distance;
        const currentSl = state.slPushed ?? p.stop_loss;
        const tighter = sign > 0 ? proposedSl > currentSl + 1e-9 : proposedSl < currentSl - 1e-9;
        // Never move SL past the mark (that would be immediate close)
        const safe = sign > 0 ? proposedSl < mark : proposedSl > mark;
        if (tighter && safe) {
          const decimals = mark < 1 ? 5 : 2;
          const newSl = Number(proposedSl.toFixed(decimals));
          const previousSl = currentSl;
          try {
            const res = await backendApi.updatePositionSlTp(p._id, {
              stop_loss: newSl,
              take_profit: p.take_profit,
            });
            state.slPushed = proposedSl;
            appendAutoExitLog({
              kind: "trailing_sl_update",
              symbol: p.coin_symbol,
              positionId: p._id,
              positionSide: p.position_side,
              entryPrice: p.entry_price,
              markPrice: mark,
              peakPrice: state.peak,
              profitPct,
              trailingActivatePct: cfg.trailingActivatePct,
              trailingDistancePct: cfg.trailingDistancePct,
              previousSl,
              newSl,
              orderStatus: res?.ok ? "submitted" : "unknown",
              note: `Peak ${state.peak.toFixed(decimals)} · profit ${profitPct.toFixed(2)}%`,
            });
            toast.message(`Trailing SL tightened · ${p.coin_symbol}`, {
              description: `New SL ${newSl.toFixed(decimals)} (peak ${state.peak.toFixed(decimals)})`,
            });
          } catch (e) {
            appendAutoExitLog({
              kind: "trailing_sl_failed",
              symbol: p.coin_symbol,
              positionId: p._id,
              positionSide: p.position_side,
              entryPrice: p.entry_price,
              markPrice: mark,
              peakPrice: state.peak,
              profitPct,
              trailingActivatePct: cfg.trailingActivatePct,
              trailingDistancePct: cfg.trailingDistancePct,
              previousSl,
              newSl,
              error: e instanceof Error ? e.message : "sl update failed",
            });
            // next tick will retry
          }
        }
      }
      hwm[p._id] = state;
    }
  }

  // Trim notified set so it doesn't grow forever
  if (notified.size > 200) {
    const arr = Array.from(notified);
    notified.clear();
    arr.slice(-100).forEach((k) => notified.add(k));
  }
}

export function AutoExitRunner() {
  const hwmRef = useRef<HwmMap>({});
  const notifiedRef = useRef<Set<string>>(new Set());

  useEffect(() => {
    let alive = true;
    const tick = async () => {
      if (!alive) return;
      await evaluate(hwmRef.current, notifiedRef.current);
    };
    tick();
    const id = setInterval(tick, POLL_MS);
    return () => {
      alive = false;
      clearInterval(id);
    };
  }, []);

  return null;
}
