// Safe evaluator for user-defined algo specs. Given a snapshot of computed
// indicator values, checks each side's rule set (AND semantics) and returns
// which side (if any) fires. No arbitrary code execution — the spec is a
// declarative rule list validated by the schema.

import type { CustomAlgoSpec, Condition } from "@/lib/algo-registry";

export type IndicatorSnapshot = {
  rsi: number;
  ema_fast_gt_slow: number; // 1 or 0
  macd_hist: number;
  price_vs_bb_upper: number; // 1 or 0
  price_vs_bb_lower: number; // 1 or 0
};

function cmp(op: Condition["op"], a: number, b: number): boolean {
  switch (op) {
    case ">": return a > b;
    case "<": return a < b;
    case ">=": return a >= b;
    case "<=": return a <= b;
    case "==": return a === b;
  }
}

function matches(rules: Condition[], snap: IndicatorSnapshot): { ok: boolean; reasons: string[] } {
  if (!rules.length) return { ok: false, reasons: [] };
  const reasons: string[] = [];
  for (const r of rules) {
    const v = snap[r.indicator];
    if (!Number.isFinite(v)) return { ok: false, reasons: [] };
    if (!cmp(r.op, v, r.value)) return { ok: false, reasons: [] };
    reasons.push(`${r.indicator} ${r.op} ${r.value} (=${Number(v).toFixed(2)})`);
  }
  return { ok: true, reasons };
}

export function evaluateCustom(
  spec: CustomAlgoSpec,
  snap: IndicatorSnapshot,
): { side: "LONG" | "SHORT"; confidence: number; reasons: string[] } | null {
  const long = matches(spec.side_rules.long, snap);
  if (long.ok) return { side: "LONG", confidence: spec.confidence, reasons: long.reasons };
  const short = matches(spec.side_rules.short, snap);
  if (short.ok) return { side: "SHORT", confidence: spec.confidence, reasons: short.reasons };
  return null;
}

/** Validate an object into a CustomAlgoSpec, throwing with a readable msg. */
export function parseCustomAlgoSpec(input: unknown): CustomAlgoSpec {
  if (!input || typeof input !== "object") throw new Error("spec must be an object");
  const o = input as Record<string, unknown>;
  const label = String(o.label ?? "").trim();
  if (!label) throw new Error("label required");
  const tf = String(o.timeframe ?? "1h") as CustomAlgoSpec["timeframe"];
  const conf = Math.min(0.95, Math.max(0.4, Number(o.confidence ?? 0.6)));
  const rules = o.side_rules as { long?: unknown; short?: unknown } | undefined;
  const validate = (arr: unknown): Condition[] => {
    if (!Array.isArray(arr)) return [];
    return arr.flatMap((c) => {
      if (!c || typeof c !== "object") return [];
      const cc = c as Record<string, unknown>;
      const ind = String(cc.indicator);
      const op = String(cc.op) as Condition["op"];
      const value = Number(cc.value);
      const validInd = ["rsi", "ema_fast_gt_slow", "macd_hist", "price_vs_bb_upper", "price_vs_bb_lower"];
      const validOp = [">", "<", ">=", "<=", "=="];
      if (!validInd.includes(ind) || !validOp.includes(op) || !Number.isFinite(value)) return [];
      return [{ indicator: ind as Condition["indicator"], op: op as Condition["op"], value }];
    });
  };
  const long = validate(rules?.long);
  const short = validate(rules?.short);
  if (long.length === 0 && short.length === 0) throw new Error("at least one side rule is required");
  const key = String(o.key ?? `custom_${label.toLowerCase().replace(/[^a-z0-9]+/g, "_")}_${Date.now().toString(36)}`);
  return {
    key,
    label,
    description: o.description ? String(o.description) : undefined,
    timeframe: (["15m", "1h", "any"] as const).includes(tf) ? tf : "1h",
    confidence: conf,
    side_rules: { long, short },
    created_at: new Date().toISOString(),
  };
}
