/**
 * MarketDataService.js
 *
 * Singleton service. Call marketData.start() once at boot.
 * After that, marketData.getTicker(symbol) is always available.
 *
 * Flow:
 *   start()
 *     → getSymbols()          load pairs from DB
 *     → ForexEngine init      register all pairs
 *     → seed from DB prices   so engine never starts at 1.0
 *     → _pollRates()          fetch live rates, anchor engine (await before first tick)
 *     → _tick()               first snapshot immediately
 *     → setInterval _pollRates  every pollMs
 *     → setInterval _tick       every tickMs
 */

const axios = require("axios");

// ForexEngine is ESM — if your server is CommonJS, use a dynamic import wrapper.
// If your server is ESM, change to: import ForexEngine from "../engine/ForexEngine.js"
const ForexEngine = require("../engine/forex/ForexEngine.js");

const { MarketPair, SymbolStats } = require("../models/index.js");

// ─── Helpers ─────────────────────────────────────────────────────────────────

function safeNum(value, fallback = 0) {
  const n = Number(value);
  return Number.isFinite(n) ? n : fallback;
}

// ─────────────────────────────────────────────────────────────────────────────

class ForexMarketService {
  constructor() {
    this._engine    = null;
    this._pairs     = [];      // [{ symbol, rate, fallback, ... }]
    this._snapshot  = {};      // { [SYMBOL]: ticker } — always populated after start()
    this._ratesPoll = null;
    this._tickTimer = null;
    this._polling   = false;   // prevents overlapping poll calls
    this._started   = false;
  }

  // ─── Startup / shutdown ────────────────────────────────────────────────────

  /**
   * Boot the service. Awaits real prices before first tick so
   * the engine never starts at the 1.0 fallback.
   *
   * @param {{ pollMs?: number, tickMs?: number }} opts
   */
  async start({ pollMs = 5_000, tickMs = 5_000 } = {}) {
    if (this._started) {
      console.warn("[MarketDataService] Already started — skipping");
      return;
    }

    // ── 1. Load pairs from DB ───────────────────────────────────────────────
    this._pairs = await this._getSymbols();
    console.log("[MarketDataService] Pairs loaded:", this._pairs);
    if (!this._pairs.length) {
      console.warn("[MarketDataService] No FX pairs found in DB — nothing to start");
      return;
    }

    const symbols = this._pairs.map(p => p.symbol);
    console.log(`[MarketDataService] Loaded ${symbols.length} pairs: ${symbols.join(", ")}`);

    // ── 2. Build engine ─────────────────────────────────────────────────────
    // ForexEngine may still be loading (dynamic import). Retry for up to 5s.
   

    this._engine = new ForexEngine(symbols, 42, { ticksPerDay: 100 });

    // ── 3. Seed engine with DB prices so no pair starts at 1.0 ─────────────
    for (const asset of this._pairs) {
      const price = safeNum(asset.rate, 0) || safeNum(asset.fallback, 1.0);
      this._engine.updatePrice(asset.symbol, price);
    }

    // ── 4. Fetch live rates NOW and anchor engine before first tick ─────────
    await this._pollRates();

    // ── 5. First tick — snapshot is immediately available ───────────────────
    this._tick();

    // ── 6. Start intervals ──────────────────────────────────────────────────
    this._ratesPoll = setInterval(() => this._pollRates(), pollMs);
    this._tickTimer = setInterval(() => this._tick(),      tickMs);

    this._started = true;
    console.log(`[MarketDataService] Running — pollMs=${pollMs} tickMs=${tickMs}`);
  }

  stop() {
    clearInterval(this._ratesPoll);
    clearInterval(this._tickTimer);
    this._ratesPoll = null;
    this._tickTimer = null;
    this._started   = false;
    console.log("[MarketDataService] Stopped");
  }

  // ─── Public getters ────────────────────────────────────────────────────────

  /**
   * Latest ticker for one pair.
   * Always returns an object after start() — never undefined.
   *
   * @param   {string} symbol   e.g. "EURUSD"
   * @returns {object|null}
   */
  getTicker(symbol) {
    return this._snapshot[String(symbol ?? "").toUpperCase()] ?? null;
  }

  /**
   * All tickers as an array.
   * @returns {object[]}
   */
  getAllTickers() {
    return Object.values(this._snapshot);
  }

  // ─── DB ───────────────────────────────────────────────────────────────────

  async _getSymbols() {
    try {
      const pairs = await MarketPair.findAll({
        where:   { type: "fx" },
        include: [{ model: SymbolStats, as: "rate" }],
      });

      return pairs.map(p => ({
        symbol:   String(p.symbol ?? "").toUpperCase(),
        base:     p.base,
        quote:    p.quote,
        name:     p.name,
        // current_price is the live-ish price stored by your sync job
        rate:     safeNum(p.rate?.current_price, 0) || safeNum(p.rate?.close, 0) || 1.0,
        fallback: safeNum(p.rate?.close, 0)         || safeNum(p.rate?.current_price, 0) || 1.0,
      })).filter(p => p.symbol);

    } catch (err) {
      console.error("[MarketDataService] _getSymbols failed:", err.message);
      return [];
    }
  }

  // ─── Rate poll ────────────────────────────────────────────────────────────

  async _pollRates() {
    // Skip if a previous poll is still in flight
    if (this._polling) return;
    this._polling = true;

    try {
      const res  = await axios.get("http://localhosts:5001/api/v1/latest/rates/type/fx", {
        timeout: 4_000,   // don't stall the tick loop
      });

      const data = res?.data?.data?.rates ?? res?.data?.rates ?? [];

      for (const d of data) {
        const symbol = String(d.symbol ?? d.pair ?? "").toUpperCase();
        if (!symbol || !this._engine?.states[symbol]) continue;

        // Accept price, mid, or close from API response
        const live = safeNum(d.price, 0) || safeNum(d.mid, 0) || safeNum(d.close, 0);
        if (live <= 0) continue;

        // Blend: 5% sim weight, 95% live — matches the React hook anchor
        const simPrice = safeNum(this._engine.assets[symbol]?.price, live);
        const blended  = simPrice * 0.05 + live * 0.95;

        this._engine.updatePrice(symbol, blended);
      }

    } catch (err) {
      // Non-fatal: engine keeps running on last anchored prices
      console.warn("[MarketDataService] _pollRates failed:", err.message);
    } finally {
      this._polling = false;
    }
  }

  // ─── Engine tick ──────────────────────────────────────────────────────────

  _tick() {
    const eng = this._engine;
    if (!eng) return;

    for (const asset of this._pairs) {
      const sym = asset.symbol;

      // Skip pairs that haven't been initialised yet (updatePrice not called)
      if (!eng.states[sym]?._initialised) continue;

      try {
        const snap = eng.tick(sym, { drift: true, book: true, trades: true });

        const quote = snap.quote     ?? {};
        const ohlc  = snap.asset     ?? {};
        const book  = snap.orderBook ?? { bids: [], asks: [] };

        this._snapshot[sym] = {
          symbol:     sym,
          price:      safeNum(quote.mid ?? ohlc.price, 0),
          bid:        safeNum(quote.bid, 0),
          ask:        safeNum(quote.ask, 0),
          spread:     quote.spread     ?? 0,
          spreadPips: quote.spreadPips ?? 0,
          open:       safeNum(ohlc.open,  0),
          high:       safeNum(ohlc.high,  0),
          low:        safeNum(ohlc.low,   0),
          close:      safeNum(ohlc.close, 0),
          volumeLots: ohlc.volumeLots ?? 0,
          pipSize:    quote.pipSize   ?? 0.0001,
          decimals:   quote.decimals  ?? 5,
          session:    snap.session    ?? {},
          orderBook:  book,
          ticks:      snap.ticks ?? [],
          updatedAt:  Date.now(),
        };

      } catch (err) {
        console.error(`[MarketDataService] tick failed for ${sym}:`, err.message);
      }
    }
  }
}

// Export singleton — one engine, shared across the whole process
module.exports = new ForexMarketService();