// services/admin/adminUserTradingService.js
const {
  sequelize,
  User,
  AssetHolding,
  AssetLot,
  Asset,
  SpotOrder,
  SpotTrade,
  DerivativeAccount,
  DerivativeOrder,
  DerivativePosition,
  DerivativeTrade,
  TradeHistory,
  MarketPair,
  UserBalance,
  Transaction,
  TraderProfile,
  CopyRelation,
  BinaryTrade,
  SignalProvider,
} = require("../../models");

const { Op } = require("sequelize");
const { NotFoundError, BadRequestError, ConflictError, ValidationError } = require("../../utils/errors");
const { generateTransactionId } = require("../../utils/helpers");
const userService = require("./userService");

function toPage(query) {
  const page = Math.max(parseInt(query.page || "1", 10), 1);
  const limit = Math.min(Math.max(parseInt(query.limit || "20", 10), 1), 200);
  const offset = (page - 1) * limit;
  return { page, limit, offset };
}

const ALLOWED = {
  holdings: ["quantity", "buyPrice", "currentPrice", "profit", "side", "status"],
  der_positions: [
    "qty", "leverage", "entry_price", "mark_price",
    "stop_loss", "take_profit", "realized_pnl", "side", "status",
  ],
  spot_orders: [
    "qty", "qty_filled", "price", "stop_price", "side", "type", "status",
  ],
  terminal: [
    "side", "volume", "entryPrice", "requestedPrice", "orderType",
    "leverage", "stopLoss", "takeProfit", "duration",
    "profitAmount", "closedAt", "status", "result",
  ],
  options: [
    "investment", "payoutPercent", "entryPrice", "resultPrice",
    "potentialProfit", "payoutAmount", "profit", "direction",
    "status", "durationSeconds",
  ],
};

/** Strip any keys not in the whitelist so callers cannot patch arbitrary columns */
function pick(obj, allowed) {
  return Object.fromEntries(
    Object.entries(obj).filter(([k]) => allowed.includes(k))
  );
}

class adminUserTradingService {

  async listPairs() {
    const pairs = await MarketPair.findAll({
      where: { tradingEnabled: true },
      include: [{ model: Asset, as: "base" }]
    })

    return pairs.map(p => ({
      symbol: p.symbol,
      name: p.name,
      logo: p.base?.logo
    }))

  }

  async listTrades(query = {}) {

    const { page, limit, offset } = toPage(query);

    const where = {};
    if (query.assetId) where.assetId = Number(query.assetId);

    const { rows, count } = await TradeHistory.findAndCountAll({
      where,
      order: [["updated_at", "DESC"]],
      limit,
      offset,
      include: MarketPair ? [{ model: MarketPair, as: "pair", required: false }] : [],
    });

    return { count, page, limit, terminal: rows };
  }

  async listUserTrades(userId, query = {}) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const { page, limit, offset } = toPage(query);

    const where = { userId: Number(userId) };
    if (query.assetId) where.assetId = Number(query.assetId);

    const { rows, count } = await TradeHistory.findAndCountAll({
      where,
      order: [["updated_at", "DESC"]],
      limit,
      offset,
      include: MarketPair ? [{ model: MarketPair, as: "pair", required: false }] : [],
    });

    return { count, page, limit, terminal: rows };
  }

  async deleteTradeHistory(tradeId) {
    const t = await sequelize.transaction();
    try {
      // ── 1. Find holding ───────────────────────────────────────────────────
      const trading = await TradeHistory.findOne({
        where: { id: tradeId },
        include: [{ model: MarketPair, as: 'pair' }],
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!trading) throw new ConflictError("Trade History not found");
      const message = `Terminal trade - ${trading?.volume} unit of ${trading?.pair?.symbol?.replace("_", "/")} deleted successfully`

      // ── 3. Delete the holding itself ──────────────────────────────────────
      await trading.destroy({ transaction: t });

      await t.commit();
      return { message };

    } catch (e) {
      await t.rollback();
      throw e;
    }
  }

  async listUserHoldings(userId, query = {}) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const { page, limit, offset } = toPage(query);

    const where = { userId: Number(userId) };
    if (query.assetId) where.assetId = Number(query.assetId);

    const { rows, count } = await TradeHistory.findAndCountAll({
      where,
      order: [["updated_at", "DESC"]],
      limit,
      offset,
      include: MarketPair ? [{ model: MarketPair, as: "pair", required: false }] : [],
    });

    return { count, page, limit, terminal: rows };
  }

  // -------------------------
  // OPTIONS
  // -------------------------
  async listOptions(query = {}) {

    const { page, limit, offset } = toPage(query);

    const where = {};
    if (query.assetId) where.assetId = Number(query.assetId);

    const { rows, count } = await BinaryTrade.findAndCountAll({
      where,
      order: [["updatedAt", "DESC"]],
      limit,
      offset,
      include: MarketPair ? [{ model: MarketPair, as: "pair", required: false }] : [],
    });

    return { count, page, limit, options: rows };
  }

  async listUserOptions(userId, query = {}) {

    const { page, limit, offset } = toPage(query);

    const where = { userId };
    if (query.assetId) where.assetId = Number(query.assetId);

    const { rows, count } = await BinaryTrade.findAndCountAll({
      where,
      order: [["updatedAt", "DESC"]],
      limit,
      offset,
      include: MarketPair ? [{ model: MarketPair, as: "pair", required: false }] : [],
    });

    return { count, page, limit, options: rows };
  }

  async deleteOptionHistory(tradeId) {
    const t = await sequelize.transaction();
    try {
      // ── 1. Find holding ───────────────────────────────────────────────────
      const trading = await BinaryTrade.findOne({
        where: { id: tradeId },
        include: [{ model: MarketPair, as: 'pair' }],
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!trading) throw new ConflictError("Binary Trade not found");
      const message = `Option trade - ${trading?.volume} unit of ${trading?.pair?.symbol?.replace("_", "/")} deleted successfully`

      // ── 3. Delete the holding itself ──────────────────────────────────────
      await trading.destroy({ transaction: t });

      await t.commit();
      return { message };

    } catch (e) {
      await t.rollback();
      throw e;
    }
  }

  // -------------------------
  // PORTFOLIO
  // -------------------------
  async listHoldings(query = {}) {

    const { page, limit, offset } = toPage(query);

    const where = {};
    if (query.assetId) where.assetId = Number(query.assetId);

    const { rows, count } = await AssetHolding.findAndCountAll({
      where,
      order: [["updated_at", "DESC"]],
      limit,
      offset,
      include: [{ model: MarketPair, as: "asset", required: true }, { model: User, as: "user", required: false, attributes: ["id", "email"] }],
    });

    return { count, page, limit, holdings: rows };
  }

  async listUserHoldings(userId, query = {}) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const { page, limit, offset } = toPage(query);

    const where = { userId: Number(userId) };
    if (query.assetId) where.assetId = Number(query.assetId);

    const { rows, count } = await AssetHolding.findAndCountAll({
      where,
      order: [["updated_at", "DESC"]],
      limit,
      offset,
      include: [{ model: MarketPair, as: "asset", required: false }, { model: User, as: "user", required: false, attributes: ["id", "email"] }],
    });

    return { count, page, limit, holdings: rows };
  }

  async addDividends(assetId, amount, type, adminId) {
    try {
      if (!['credit', 'debit', 'reset', 'clear'].includes(type)) {
        throw new Error("Invalid dividend operation type");
      }

      amount = Number(amount);
      if (isNaN(amount) || amount < 0) {
        throw new Error("Invalid amount");
      }

      return await sequelize.transaction(async (t) => {

        const holding = await AssetHolding.findOne({
          where: { id: Number(assetId) },
          include: MarketPair ? [{ model: MarketPair, as: "asset", required: false }] : [],
          transaction: t,
          lock: t.LOCK.UPDATE,
        });

        if (!holding) throw new NotFoundError("Asset holding not found");

        const user = await User.findByPk(holding.userId, {
          transaction: t,
          lock: t.LOCK.UPDATE,
        });

        if (!user) throw new NotFoundError("User not found");

        const balance = await UserBalance.findOne({
          where: { userId: Number(user.id) },
          transaction: t,
          lock: t.LOCK.UPDATE,
        });

        if (!balance) throw new NotFoundError("User balance not found");

        const now = new Date();

        if (type === 'credit') {
          holding.dividends = Number(holding.dividends || 0) + amount;
          balance.balance = Number(balance.balance) + amount;

          await holding.save({ transaction: t });
          await balance.save({ transaction: t });

          userService.notifyUser(user.userId, {
            title: "Dividends Added",
            message: `An amount of ${amount} has been credited to your dividends for asset ${holding.asset?.symbol || holding.assetId}. Your new dividends balance for this asset is ${holding.dividends}.`,
            type: "investment",
            channels: { inApp: true, push: true },
            priority: "medium",
          }, adminId).catch(err => console.error("Holding adjustment failed:", err.message));

        } else if (type === 'debit') {
          if (Number(holding.dividends || 0) < amount) {
            throw new Error("Insufficient dividends");
          }

          holding.dividends = Number(holding.dividends) - amount;
          await holding.save({ transaction: t });

        } else if (type === 'reset') {
          holding.dividends = amount;
          await holding.save({ transaction: t });

        } else if (type === 'clear') {
          holding.dividends = 0;
          await holding.save({ transaction: t });
        }

        return holding;
      });

    } catch (error) {
      console.error("Error in addDividends:", error);

      // Optional: normalize errors for API
      throw new Error(error.message || "Failed to process dividends");
    }
  }

  async addLots(data, currency = "USD") {
    const {
      assetId: holdingId,
      side,
      tradeType,
      amount,
      source,
      price,
      sourceId = null,
    } = data;

    const qty = Number(amount ?? 0);
    const px = Number(price ?? 0);

    if (!qty || qty <= 0) throw new ValidationError("Invalid amount");
    if (!px || px <= 0) throw new ValidationError("Invalid price");

    const lotCost = px * qty;

    const t = await sequelize.transaction();
    try {
      // ── 1. Find holding first — userId lives here ─────────────────────────
      const holding = await AssetHolding.findOne({
        where: { id: holdingId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!holding) throw new ConflictError("Asset holding not found");

      const userId = holding.userId;   // <── sourced from holding
      const realAssetId = holding.assetId;
      const realHoldingId = holding.id;

      const pairs = await MarketPair.findOne({
        where: { id: realAssetId },
      });
      if (!pairs) throw new ConflictError("Market Pair not found");


      // ── 2. Validate source relationship ───────────────────────────────────
      let resolvedTraderId = null;
      let resolvedSignalId = null;
      let resolvedBotId = null;

      if (source === "copy") {
        const rel = await CopyRelation.findOne({
          where: { copierId: userId, traderProfileId: sourceId },
        });
        if (!rel) throw new ConflictError("No active copy relationship with this trader");
        resolvedTraderId = sourceId;
      }

      if (source === "signal") {
        const sub = await SignalSubscription.findOne({
          where: { userId, signalId: sourceId, status: "active" },
        });
        if (!sub) throw new ConflictError("No active signal subscription");
        resolvedSignalId = sourceId;
      }

      if (source === "bot") {
        const sub = await BotSubscription.findOne({
          where: { userId, botId: sourceId, status: "active" },
        });
        if (!sub) throw new ConflictError("No active bot subscription");
        resolvedBotId = sourceId;
      }

      // ── 3. Lock balance ───────────────────────────────────────────────────
      const balance = await UserBalance.findOne({
        where: { userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!balance) throw new ConflictError("User balance not found");
      const currentBalance = Number(balance.balance || 0);

      // ── BUY ───────────────────────────────────────────────────────────────
      if (side === "buy") {
        if (currentBalance < lotCost)
          throw new ValidationError("Insufficient balance for this trade");

        const oldQty = Number(holding.quantity || 0);
        const oldCost = Number(holding.totalCost || 0);
        const newQty = oldQty + qty;
        const newTotalCost = oldCost + lotCost;

        await holding.update(
          {
            quantity: newQty,
            totalCost: newTotalCost,
            avgBuyPrice: newTotalCost / newQty,
          },
          { transaction: t }
        );

        await balance.update(
          { balance: currentBalance - lotCost },
          { transaction: t }
        );
      }

      // ── SELL ──────────────────────────────────────────────────────────────
      if (side === "sell") {
        const oldQty = Number(holding.quantity || 0);
        if (oldQty < qty) throw new ValidationError("Insufficient asset quantity");

        const avg = Number(holding.avgBuyPrice || 0);
        const newQty = oldQty - qty;
        const newTotalCost = Math.max(0, Number(holding.totalCost || 0) - avg * qty);

        await holding.update(
          { quantity: newQty, totalCost: newTotalCost },
          { transaction: t }
        );

        await balance.update(
          { balance: currentBalance + lotCost },
          { transaction: t }
        );
      }

      // ── 4. Create lot ─────────────────────────────────────────────────────
      const assetLot = await AssetLot.create(
        {
          userId,
          assetId: realHoldingId,
          quantity: qty,
          buyPrice: px,
          side,
          type: "market",
          tradeType,
          traderId: resolvedTraderId,
          signalId: resolvedSignalId,
          botId: resolvedBotId,
          status: "closed",
          timestamp: new Date(),
        },
        { transaction: t }
      );

      // ── 5. Transaction record ─────────────────────────────────────────────
      const tx = await Transaction.create(
        {
          userId,
          source: "funding",
          toType: "internal",
          kind: side === "buy" ? "buy_asset" : "sell_asset",
          direction: side === "buy" ? "debit" : "credit",
          currency,
          description: `${side === "buy" ? "Bought" : "Sold"} asset (admin lot)`,
          assetLotId: assetLot.id,
          amount: lotCost,
          netAmount: lotCost,
          status: "completed",
        },
        { transaction: t }
      );

      await t.commit();
      return { transaction: tx, assetLot, message: `Bought  lots to ` };

    } catch (e) {
      await t.rollback();
      throw e;
    }
  }

  async deleteHolding(holdingId) {
    const t = await sequelize.transaction();
    try {
      // ── 1. Find holding ───────────────────────────────────────────────────
      const holding = await AssetHolding.findOne({
        where: { id: holdingId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!holding) throw new ConflictError("Asset holding not found");

      // ── 2. Delete all lots tied to this holding ───────────────────────────
      await AssetLot.destroy({
        where: { assetId: holdingId },
        transaction: t,
      });

      // ── 3. Delete the holding itself ──────────────────────────────────────
      await holding.destroy({ transaction: t });

      await t.commit();
      return { message: "Holding and all related lots deleted successfully" };

    } catch (e) {
      await t.rollback();
      throw e;
    }
  }

  async listLots(query = {}) {


    const { page, limit, offset } = toPage(query);

    const where = {};
    if (query.assetId) where.assetId = Number(query.assetId);
    if (query.status) where.status = query.status;

    const { rows, count } = await AssetLot.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit,
      offset,
      include: MarketPair ? [{ model: MarketPair, as: "asset", required: false }] : [],
    });

    return { count, page, limit, lots: rows };
  }

  async listUserLots(userId, query = {}) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const { page, limit, offset } = toPage(query);

    const where = { userId: Number(userId) };
    if (query.assetId) where.assetId = Number(query.assetId);
    if (query.status) where.status = query.status;

    const { rows, count } = await AssetLot.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit,
      offset,
      include: MarketPair ? [{ model: MarketPair, as: "asset", required: false }] : [],
    });

    return { count, page, limit, lots: rows };
  }

  /**
   * Adjust holding (ADMIN ONLY)
   * body:
   *  - assetId (required)
   *  - operation: 'set' | 'add' | 'subtract' (default: 'set')
   *  - quantity (required)
   *  - avgBuyPrice (optional)
   *  - totalCost (optional)
   */
  async adjustHolding(userId, body = {}) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const assetId = Number(body.assetId);
    if (!assetId) throw new BadRequestError("assetId is required");

    const op = String(body.operation || "set").toLowerCase();
    if (!["set", "add", "subtract"].includes(op)) {
      throw new BadRequestError("operation must be set, add, or subtract");
    }

    const qty = Number(body.quantity);
    if (!Number.isFinite(qty)) throw new BadRequestError("quantity must be a number");

    const avgBuyPrice =
      body.avgBuyPrice === undefined || body.avgBuyPrice === null ? null : Number(body.avgBuyPrice);
    if (avgBuyPrice !== null && !Number.isFinite(avgBuyPrice)) {
      throw new BadRequestError("avgBuyPrice must be a number");
    }

    const totalCost =
      body.totalCost === undefined || body.totalCost === null ? null : Number(body.totalCost);
    if (totalCost !== null && !Number.isFinite(totalCost)) {
      throw new BadRequestError("totalCost must be a number");
    }

    return await sequelize.transaction(async (t) => {
      const [holding] = await AssetHolding.findOrCreate({
        where: { userId: Number(userId), assetId },
        defaults: { quantity: 0, avgBuyPrice: 0, totalCost: 0 },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });

      const currentQty = Number(holding.quantity || 0);

      let newQty = currentQty;
      if (op === "set") newQty = qty;
      if (op === "add") newQty = currentQty + qty;
      if (op === "subtract") newQty = currentQty - qty;

      if (!Number.isFinite(newQty)) throw new BadRequestError("invalid resulting quantity");
      if (newQty < 0) throw new BadRequestError("resulting quantity cannot be negative");

      holding.quantity = newQty;

      // If provided, set explicitly; else keep consistent if possible
      if (avgBuyPrice !== null) holding.avgBuyPrice = avgBuyPrice;
      if (totalCost !== null) holding.totalCost = totalCost;

      // simple consistency: if avgBuyPrice not provided but totalCost provided, derive avg
      if (avgBuyPrice === null && totalCost !== null) {
        holding.avgBuyPrice = newQty > 0 ? Number(totalCost) / Number(newQty) : 0;
      }

      await holding.save({ transaction: t });
      return holding;
    });
  }

  // -------------------------
  // SPOT
  // -------------------------
  async listSpotOrders(query = {}) {

    const { page, limit, offset } = toPage(query);

    const where = {};
    if (query.marketId) where.marketId = Number(query.marketId);
    if (query.status) where.status = query.status;
    if (query.side) where.side = query.side;

    // your model uses user_id/market_id fields; sequelize will map underscored if defined that way.
    // We support both by querying column names if needed.
    const { rows, count } = await SpotOrder.findAndCountAll({
      where: {
        ...(where.marketId ? { market_id: where.marketId } : {}),
        ...(where.status ? { status: where.status } : {}),
        ...(where.side ? { side: where.side } : {}),
      },
      order: [["created_at", "DESC"]],
      limit,
      offset,
    });

    return { count, page, limit, orders: rows };
  }

  async listUserSpotOrders(userId, query = {}) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const { page, limit, offset } = toPage(query);

    const where = { userId: Number(userId) };
    if (query.marketId) where.marketId = Number(query.marketId);
    if (query.status) where.status = query.status;
    if (query.side) where.side = query.side;

    // your model uses user_id/market_id fields; sequelize will map underscored if defined that way.
    // We support both by querying column names if needed.
    const { rows, count } = await SpotOrder.findAndCountAll({
      where: {
        ...(where.userId ? { user_id: where.userId } : {}),
        ...(where.marketId ? { market_id: where.marketId } : {}),
        ...(where.status ? { status: where.status } : {}),
        ...(where.side ? { side: where.side } : {}),
      },
      order: [["created_at", "DESC"]],
      limit,
      offset,
    });

    return { count, page, limit, orders: rows };
  }

  async cancelSpotOrder(userId, orderId) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    return await sequelize.transaction(async (t) => {
      const order = await SpotOrder.findOne({
        where: { id: Number(orderId), user_id: Number(userId) },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!order) throw new NotFoundError("Spot order not found");

      const status = String(order.status || "").toLowerCase();
      if (!["new", "partially_filled"].includes(status)) {
        throw new BadRequestError(`Order cannot be canceled from status: ${order.status}`);
      }

      order.status = "canceled";
      await order.save({ transaction: t });
      return order;
    });
  }

  async listSpotTrades(query = {}) {

    const { page, limit, offset } = toPage(query);
    // spot_trades has maker_user_id / taker_user_id
    const where = {
    };
    if (query.marketId) where.market_id = Number(query.marketId);

    const { rows, count } = await SpotTrade.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit,
      offset,
    });

    return { count, page, limit, trades: rows };
  }

  async listUserSpotTrades(userId, query = {}) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const { page, limit, offset } = toPage(query);

    // spot_trades has maker_user_id / taker_user_id
    const where = {
      [Op.or]: [{ maker_user_id: Number(userId) }, { taker_user_id: Number(userId) }],
    };
    if (query.marketId) where.market_id = Number(query.marketId);

    const { rows, count } = await SpotTrade.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit,
      offset,
    });

    return { count, page, limit, trades: rows };
  }

  // -------------------------
  // DERIVATIVES
  // -------------------------
  async listDerivativeAccounts(userId, query = {}) {


    const { page, limit, offset } = toPage(query);

    const where = {};
    if (query.kind) where.kind = query.kind;
    if (query.status) where.status = query.status;

    const { rows, count } = await DerivativeAccount.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit,
      offset,
    });

    return { count, page, limit, accounts: rows };
  }

  async listUserDerivativeAccounts(userId, query = {}) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const { page, limit, offset } = toPage(query);

    const where = { user_id: Number(userId) };
    if (query.kind) where.kind = query.kind;
    if (query.status) where.status = query.status;

    const { rows, count } = await DerivativeAccount.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit,
      offset,
    });

    return { count, page, limit, accounts: rows };
  }

  async setDerivativeAccountStatus(userId, accountId, status) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    if (!["active", "restricted", "liquidating"].includes(status)) {
      throw new BadRequestError("Invalid status");
    }

    return await sequelize.transaction(async (t) => {
      const account = await DerivativeAccount.findOne({
        where: { id: Number(accountId), user_id: Number(userId) },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!account) throw new NotFoundError("Derivative account not found");

      account.status = status;
      await account.save({ transaction: t });
      return account;
    });
  }

  async listDerivativeOrders(query = {}) {

    const { page, limit, offset } = toPage(query);

    // DerivativeOrder ties to account_id; we fetch user's accounts then filter by those IDs
    const accounts = await DerivativeAccount.findAll({
      attributes: ["id"],
    });
    const accountIds = accounts.map((a) => a.id);

    if (!accountIds.length) return { count: 0, page, limit, orders: [] };

    const where = { account_id: { [Op.in]: accountIds } };
    if (query.accountId) where.account_id = Number(query.accountId);
    if (query.marketId) where.market_id = Number(query.marketId);
    if (query.status) where.status = query.status;
    if (query.side) where.side = query.side;

    const { rows, count } = await DerivativeOrder.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit,
      offset,
    });

    return { count, page, limit, orders: rows };
  }

  async listUserDerivativeOrders(userId, query = {}) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const { page, limit, offset } = toPage(query);

    // DerivativeOrder ties to account_id; we fetch user's accounts then filter by those IDs
    const accounts = await DerivativeAccount.findAll({
      where: { user_id: Number(userId) },
      attributes: ["id"],
    });
    const accountIds = accounts.map((a) => a.id);

    if (!accountIds.length) return { count: 0, page, limit, orders: [] };

    const where = { account_id: { [Op.in]: accountIds } };
    if (query.accountId) where.account_id = Number(query.accountId);
    if (query.marketId) where.market_id = Number(query.marketId);
    if (query.status) where.status = query.status;
    if (query.side) where.side = query.side;

    const { rows, count } = await DerivativeOrder.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit,
      offset,
    });

    return { count, page, limit, orders: rows };
  }

  async cancelDerivativeOrder(userId, orderId) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    return await sequelize.transaction(async (t) => {
      const order = await DerivativeOrder.findByPk(Number(orderId), {
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!order) throw new NotFoundError("Derivative order not found");

      // confirm order belongs to user through account
      const account = await DerivativeAccount.findOne({
        where: { id: order.account_id, user_id: Number(userId) },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!account) throw new NotFoundError("Order does not belong to this user");

      const status = String(order.status || "").toLowerCase();
      if (!["new", "partially_filled"].includes(status)) {
        throw new BadRequestError(`Order cannot be canceled from status: ${order.status}`);
      }

      order.status = "canceled";
      await order.save({ transaction: t });
      return order;
    });
  }

  async listDerivativePositions(query = {}) {

    const { page, limit, offset } = toPage(query);

    const accounts = await DerivativeAccount.findAll({
      attributes: ["id"],
    });
    const accountIds = accounts.map((a) => a.id);

    if (!accountIds.length) return { count: 0, page, limit, positions: [] };

    const where = { account_id: { [Op.in]: accountIds } };
    if (query.accountId) where.account_id = Number(query.accountId);
    if (query.marketId) where.market_id = Number(query.marketId);
    if (query.status) where.status = query.status;

    const { rows, count } = await DerivativePosition.findAndCountAll({
      where,
      order: [["opened_at", "DESC"]],
      limit,
      offset,
      include: [{ model: User, as: 'user' }, { model: MarketPair, as: 'pair' }]
    });

    return { count, page, limit, positions: rows };
  }

  async listUserDerivativePositions(userId, query = {}) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const { page, limit, offset } = toPage(query);

    const accounts = await DerivativeAccount.findAll({
      where: { user_id: Number(userId) },
      attributes: ["id"],
    });
    const accountIds = accounts.map((a) => a.id);

    if (!accountIds.length) return { count: 0, page, limit, positions: [] };

    const where = { account_id: { [Op.in]: accountIds } };
    if (query.accountId) where.account_id = Number(query.accountId);
    if (query.marketId) where.market_id = Number(query.marketId);
    if (query.status) where.status = query.status;

    const { rows, count } = await DerivativePosition.findAndCountAll({
      where,
      order: [["opened_at", "DESC"]],
      limit,
      offset,
      include: [{ model: User, as: 'user' }, { model: MarketPair, as: 'pair' }]
    });

    return { count, page, limit, positions: rows };
  }

  async deleteDerivativePositions(positionId) {
    const t = await sequelize.transaction();
    try {
      // ── 1. Find holding ───────────────────────────────────────────────────
      const position = await DerivativePosition.findOne({
        where: { id: positionId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!position) throw new ConflictError("Asset holding not found");
      const tPosition = position?.order_id

      // ── 2. Delete all lots tied to this holding ───────────────────────────


      await DerivativeTrade.destroy({
        where: { taker_order_id: tPosition },
        transaction: t,
      });

      // ── 3. Delete the holding itself ──────────────────────────────────────
      await position.destroy({ transaction: t });

      await DerivativeOrder.destroy({
        where: { id: tPosition },
        transaction: t,
      });

      await t.commit();
      return { message: "This Position and all related lots deleted successfully" };

    } catch (e) {
      await t.rollback();
      throw e;
    }
  }


  async listDerivativeTrades(query = {}) {

    const { page, limit, offset } = toPage(query);

    const accounts = await DerivativeAccount.findAll({
      attributes: ["id"],
    });
    const accountIds = accounts.map((a) => a.id);

    if (!accountIds.length) return { count: 0, page, limit, trades: [] };

    const where = {
      [Op.or]: [
        { maker_account_id: { [Op.in]: accountIds } },
        { taker_account_id: { [Op.in]: accountIds } },
      ],
    };
    if (query.marketId) where.market_id = Number(query.marketId);

    const { rows, count } = await DerivativeTrade.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit,
      offset,
    });

    return { count, page, limit, trades: rows };
  }

  async listUserDerivativeTrades(userId, query = {}) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const { page, limit, offset } = toPage(query);

    const accounts = await DerivativeAccount.findAll({
      where: { user_id: Number(userId) },
      attributes: ["id"],
    });
    const accountIds = accounts.map((a) => a.id);

    if (!accountIds.length) return { count: 0, page, limit, trades: [] };

    const where = {
      [Op.or]: [
        { maker_account_id: { [Op.in]: accountIds } },
        { taker_account_id: { [Op.in]: accountIds } },
      ],
    };
    if (query.marketId) where.market_id = Number(query.marketId);

    const { rows, count } = await DerivativeTrade.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit,
      offset,
    });

    return { count, page, limit, trades: rows };
  }


  async tradeStock(userId, data, currency = "USD") {
    const { asset: symbol, side, price, marketType, limit, tradeType = "auto", traderId = null, quantity } = data;

    const qty = Number(quantity ?? 0);
    const px = Number(price ?? 0);

    if (!qty || qty <= 0) throw new ValidationError("Invalid amount");
    if (!px || px <= 0) throw new ValidationError("Invalid price");

    if (tradeType === 'copy') {
      const copyrelations = await CopyRelation.findOne({
        where: {
          copierId: userId,
          traderProfileId: traderId
        }
      })
      if (!copyrelations) throw new ConflictError("Invalid copytrader");
    }

    const lotCost = px * qty;

    const asset = await MarketPair.findOne({
      where: { symbol, status: "active" },
    });

    if (!asset) throw new ConflictError(`${symbol} not available for trading`);

    const t = await sequelize.transaction();
    try {
      const balance = await UserBalance.findOne({
        where: { userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });

      if (!balance) throw new ConflictError("User must have a user balance");

      const currentBalance = Number(balance.balance || 0);

      // Lock holdings row too (if exists)
      let holding = await AssetHolding.findOne({
        where: { userId, assetId: asset.id },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });

      // ---- BUY ----
      if (side === "buy") {
        if (currentBalance < lotCost) {
          throw new ValidationError("Insufficient balance for this trade");
        }

        if (holding) {
          const oldQty = Number(holding.quantity || 0);
          const oldAvg = Number(holding.avgBuyPrice || 0);
          const oldCost = Number(holding.totalCost || 0);

          // Weighted average
          const newQty = oldQty + qty;
          const newTotalCost = oldCost + lotCost;
          const newAvg = newTotalCost / newQty;

          await holding.update(
            { quantity: newQty, totalCost: newTotalCost, avgBuyPrice: newAvg },
            { transaction: t }
          );
        } else {
          holding = await AssetHolding.create(
            {
              userId,
              assetId: asset.id,
              quantity: qty,
              avgBuyPrice: px,
              totalCost: lotCost,
            },
            { transaction: t }
          );
        }

        await balance.update(
          { balance: currentBalance - lotCost },
          { transaction: t }
        );
      }

      // ---- SELL ----
      if (side === "sell") {
        if (!holding) throw new ValidationError("You don't own this asset");

        const oldQty = Number(holding.quantity || 0);
        if (oldQty < qty) throw new ValidationError("Insufficient asset quantity");

        // for simple accounting: reduce quantity; keep avgBuyPrice
        const newQty = oldQty - qty;

        // If you track totalCost as cost basis, reduce proportionally by avg
        const avg = Number(holding.avgBuyPrice || 0);
        const newTotalCost = Math.max(0, Number(holding.totalCost || 0) - avg * qty);

        await holding.update(
          { quantity: newQty, totalCost: newTotalCost },
          { transaction: t }
        );

        // credit proceeds (qty * px). If you have fees, subtract fees here.
        await balance.update(
          { balance: currentBalance + lotCost },
          { transaction: t }
        );
      }
      const payload = {
        userId,
        assetId: asset.id,
        quantity: qty,
        buyPrice: px,
        side,
        type: marketType,
        traderId: traderId,
        tradeType,
        marketLimit: limit ?? null,
        status: "closed",
      }
      if (tradeType === 'copy') payload.traderId = traderId
      // Create lot record
      const assetLot = await AssetLot.create(
        payload,
        { transaction: t }
      );

      // Create transaction record
      const tx = await Transaction.create(
        {
          userId,
          source: "funding",
          toType: "internal",
          kind: side === "buy" ? "buy_asset" : "sell_asset",
          direction: side === "buy" ? "debit" : "credit",
          currency,
          description: `${side === "buy" ? "Bought" : "Sold"} ${symbol}`,
          assetLotId: assetLot.id,
          amount: lotCost,
          netAmount: lotCost,
          status: "completed",
        },
        { transaction: t }
      );

      await t.commit();

      const holdings = await AssetHolding.findAll({
        where: { userId },
        order: [["createdAt", "DESC"]],
        include: [{ model: MarketPair, as: 'asset', include: [{ model: Asset, as: 'base' }] }],
        limit: 5,
      });

      const lots = await AssetLot.findAll({
        where: { userId },
        order: [["createdAt", "DESC"]],
        include: [{ model: MarketPair, as: 'asset', include: [{ model: Asset, as: 'base' }] }],
        limit: 5,
      });

      return {
        transaction: tx, assetTrades: lots.map(t => ({
          id: t.id,
          symbol: t.asset?.symbol || "—",
          logo: t.asset?.base?.logo || null,
          type: t.side === "buy" ? "Buy" : "Sell",
          orderType: t.type.charAt(0).toUpperCase() + t.type.slice(1),
          price: t.entryPrice || t.buyPrice,
          shares: t.quantity || 0,
          filled: t.status === "closed" ? t.quantity : 0,
          status: t.status.charAt(0).toUpperCase() + t.status.slice(1),
          time: new Date(t.createdAt).toISOString().replace('T', ' ').substring(0, 19)
        })),
        assetHoldings: holdings.map(h => (
          {
            symbol: h.asset?.symbol || "—",
            shares: h.quantity || 0,
            avgCost: h.avgBuyPrice || 0,
            logo: h.asset?.base?.logo || null,
            currentPrice: h.asset?.currentPrice || 0,
            value: ((h.quantity || 0) * (h.asset?.currentPrice || 0)).toFixed(2),
            pnl: (((h.asset?.currentPrice || 0) - (h.avgBuyPrice || 0)) * (h.quantity || 0)).toFixed(2),
            pnlPercent: h.avgBuyPrice > 0 ? (((h.asset?.currentPrice || 0) - (h.avgBuyPrice || 0)) / (h.avgBuyPrice || 1) * 100).toFixed(2) : "0"
          }))
      };
    } catch (e) {
      await t.rollback();
      throw e;
    }
  }

  async tradeTerminal(userId, data, currency = "USD") {
    const {
      asset,
      side,
      marketType = "market",
      limit,
      quantity,
      price,
      leverage,
      duration,
      stopLoss,
      takeProfit,
      tradeType = "auto",
      traderId = null
    } = data;

    // 1) Validate
    if (!asset) throw new BadRequestError("asset is required");
    if (!["buy", "sell"].includes(side)) throw new BadRequestError("side must be buy or sell");

    const qty = Number(quantity);
    if (!Number.isFinite(qty) || qty <= 0) throw new BadRequestError("quantity must be > 0");

    /*const px = Number(price);
    if (!Number.isFinite(px) || px <= 0) throw new BadRequestError("price must be > 0");*/
    const px = price ?? 1

    if (tradeType === 'copy') {
      const copyrelations = await CopyRelation.findOne({
        where: {
          copierId: userId,
          traderProfileId: traderId
        }
      })
      if (!copyrelations) throw new ConflictError("Invalid copytrader");
    }

    const lev = Number(leverage);
    if (!Number.isFinite(lev) || lev <= 0) throw new BadRequestError("leverage must be > 0");

    const dur = Number(duration);
    if (!Number.isFinite(dur) || dur <= 0) throw new BadRequestError("duration must be > 0");

    if (marketType === "limit") {
      const lim = Number(limit);
      if (!Number.isFinite(lim) || lim <= 0) {
        throw new BadRequestError("limit is required for limit orders");
      }
    }

    // 2) Asset exists + tradable
    const assetRow = await MarketPair.findOne({
      where: { symbol: asset, status: 'active' },
    });
    if (!assetRow) throw new NotFoundError("Trading Pair not found or not tradable");

    // 3) Anti-spam duplicate pending
    const existing = await TradeHistory.findOne({
      where: {
        userId,
        pairId: asset,
        side: side,
        status: { [Op.in]: ["pending", "open", "initiated", "processing"] },
        createdAt: { [Op.gte]: new Date(Date.now() - 30 * 1000) }, // last 30s
      },
      order: [["created_at", "DESC"]],
    });
    if (existing) {
      throw new BadRequestError("You already have a pending trade for this asset. Please wait.");
    }

    // 4) Financials (example)
    // Example margin model: cost = (qty * px) / leverage
    //const marginRequired = (qty * px) / lev;

    const t = await sequelize.transaction();
    try {
      // Lock user balance row
      const balance = await UserBalance.findOne({
        where: { userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!balance) throw new ConflictError("User must have a wallet/balance");

      const currentBalance = Number(balance.balance ?? 0);
      if (currentBalance < qty) {
        throw new BadRequestError("Insufficient balance for this trade");
      }

      // Deduct margin (simple example)
      balance.balance = currentBalance - qty;
      await balance.save({ transaction: t });

      // Create transaction record (audit trail)
      const tx = await Transaction.create(
        {
          transactionId: generateTransactionId("OD"),
          userId,
          source: "funding",
          kind: "trade_margin",
          direction: "debit",
          toType: 'internal',
          currency,
          amount: qty,
          status: "completed",
          description: `${side.toUpperCase()} ${qty} ${asset} @ ${px} (x${lev})`,
          metadata: {
            asset,
            side,
            marketType,
            limit: marketType === "limit" ? Number(limit) : null,
            quantity: qty,
            price: px,
            leverage: lev,
            duration: dur,
          },
        },
        { transaction: t }
      );
      const payload = {
        orderId: tx.transactionId,
        userId,
        pairId: assetRow.id,
        side,
        orderType: marketType,
        volume: qty,
        entryPrice: px,
        requestedPrice: limit,
        leverage: lev,
        duration: dur,
        takeProfit: takeProfit ?? null,
        stopLoss: stopLoss ?? null,
        status: "open",
        tradedBy: tradeType
      }
      if (tradeType === 'copy') payload.traderId = traderId
      console.log("payloads: ", payload)
      // Create trade history (your table)
      const trade = await TradeHistory.create(
        payload,
        { transaction: t }
      );

      await t.commit();
      return { tx, trade };
    } catch (e) {
      await t.rollback();
      throw e;
    }
  }

// services/adminUserTradingService.js  (edit section)

// ═════════════════════════════════════════════════════════════════════════════
// 1. HOLDINGS
// ═════════════════════════════════════════════════════════════════════════════
async editHolding(holdingId, fields, updateBalance = false) {
  const updates = pick(fields, ALLOWED.holdings);
  if (!Object.keys(updates).length) throw new ValidationError("No valid fields provided");

  const t = await sequelize.transaction();
  try {
    const holding = await AssetHolding.findOne({
      where: { id: holdingId },
      transaction: t,
      lock: t.LOCK.UPDATE,
    });
    if (!holding) throw new NotFoundError("Holding not found");

    const userId = holding.userId;
    await holding.update(updates, { transaction: t });

    if (updateBalance) {
      const balance = await UserBalance.findOne({
        where: { userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!balance) throw new NotFoundError("User balance not found");

      const profitDelta = updates.profit != null ? Number(updates.profit) : 0;
      if (profitDelta !== 0) {
        await balance.update(
          { balance: Number(balance.balance) + profitDelta },
          { transaction: t }
        );
      }
    }

    await t.commit();
    return { holding };
  } catch (e) {
    await t.rollback();
    throw e;
  }
}

// ═════════════════════════════════════════════════════════════════════════════
// 2. DERIVATIVE POSITIONS
// ═════════════════════════════════════════════════════════════════════════════
async editDerivativePosition(positionId, fields, updateBalance = false) {
  const updates = pick(fields, ALLOWED.der_positions);
  if (!Object.keys(updates).length) throw new ValidationError("No valid fields provided");

  const t = await sequelize.transaction();
  try {
    const position = await DerivativePosition.findOne({
      where: { id: positionId },
      transaction: t,
      lock: t.LOCK.UPDATE,
    });
    if (!position) throw new NotFoundError("Derivative position not found");

    if (updates.status) {
      const statusMap = { active: "open", closed: "closed", pending: "open" };
      updates.status = statusMap[updates.status] ?? updates.status;
    }

    const prevPnl = Number(position.realized_pnl || 0);
    await position.update(updates, { transaction: t });

    if (updateBalance) {
      const account = await DerivativeAccount.findOne({
        where: { id: position.account_id },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!account) throw new NotFoundError("Derivative account not found");

      const newPnl = Number(updates.realized_pnl ?? prevPnl);
      const delta = newPnl - prevPnl;
      await account.update(
        { balance: Number(account.balance) + delta },
        { transaction: t }
      );
    }

    await t.commit();
    return { position };
  } catch (e) {
    await t.rollback();
    throw e;
  }
}

// ═════════════════════════════════════════════════════════════════════════════
// 3. SPOT ORDERS
// ═════════════════════════════════════════════════════════════════════════════
async editSpotOrder(orderId, fields, updateBalance = false) {
  const updates = pick(fields, ALLOWED.spot_orders);
  if (!Object.keys(updates).length) throw new ValidationError("No valid fields provided");

  const t = await sequelize.transaction();
  try {
    const order = await SpotOrder.findOne({
      where: { id: orderId },
      transaction: t,
      lock: t.LOCK.UPDATE,
    });
    if (!order) throw new NotFoundError("Spot order not found");

    const userId = order.user_id;
    const prevQtyFilled = Number(order.qty_filled || 0);
    const prevPrice = Number(order.price || 0);

    await order.update(updates, { transaction: t });

    if (updateBalance) {
      const pair = await MarketPair.findOne({
        where: { id: order.market_id },
        include: [
          { model: Asset, as: "base" },
          { model: Asset, as: "quote" },
        ],
        transaction: t,
      });
      if (!pair) throw new NotFoundError("Market pair not found for this order");

      const spotPrice = Number(updates.price ?? prevPrice);
      const newQtyFilled = Number(updates.qty_filled ?? prevQtyFilled);
      const filledDelta = newQtyFilled - prevQtyFilled;

      if (filledDelta !== 0) {
        const baseWallet = await Wallet.findOne({
          where: { coinId: pair.base.id, userId },
          transaction: t,
          lock: t.LOCK.UPDATE,
        });
        const quoteWallet = await Wallet.findOne({
          where: { coinId: pair.quote.id, userId },
          transaction: t,
          lock: t.LOCK.UPDATE,
        });

        if (!baseWallet || !quoteWallet) throw new NotFoundError("User coin wallet(s) not found");

        const quoteValue = filledDelta * spotPrice;

        if (order.side === "buy") {
          await baseWallet.update(
            { balance: Number(baseWallet.balance) + filledDelta },
            { transaction: t }
          );
          await quoteWallet.update(
            { balance: Number(quoteWallet.balance) - quoteValue },
            { transaction: t }
          );
        } else {
          await baseWallet.update(
            { balance: Number(baseWallet.balance) - filledDelta },
            { transaction: t }
          );
          await quoteWallet.update(
            { balance: Number(quoteWallet.balance) + quoteValue },
            { transaction: t }
          );
        }
      }
    }

    await t.commit();
    return { order };
  } catch (e) {
    await t.rollback();
    throw e;
  }
}

// ═════════════════════════════════════════════════════════════════════════════
// 4. TERMINAL (TradeHistory)
// ═════════════════════════════════════════════════════════════════════════════
async editTerminalTrade(tradeId, fields, updateBalance = false) {
  const updates = pick(fields, ALLOWED.terminal);
  if (!Object.keys(updates).length) throw new ValidationError("No valid fields provided");

  const t = await sequelize.transaction();
  try {
    const trade = await TradeHistory.findOne({
      where: { id: tradeId },
      transaction: t,
      lock: t.LOCK.UPDATE,
    });
    if (!trade) throw new NotFoundError("Trade not found");

    const userId = trade.userId;
    const prevProfit = Number(trade.profitAmount || 0);
    await trade.update(updates, { transaction: t });

    if (updateBalance) {
      const balance = await UserBalance.findOne({
        where: { userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!balance) throw new NotFoundError("User balance not found");

      const newProfit = Number(updates.profitAmount ?? prevProfit);
      const delta = newProfit - prevProfit;
      if (delta !== 0) {
        await balance.update(
          { balance: Number(balance.balance) + delta },
          { transaction: t }
        );
      }
    }

    await t.commit();
    return { trade };
  } catch (e) {
    await t.rollback();
    throw e;
  }
}

// ═════════════════════════════════════════════════════════════════════════════
// 5. BINARY OPTIONS
// ═════════════════════════════════════════════════════════════════════════════
async editBinaryOption(tradeId, fields, updateBalance = false) {
  const updates = pick(fields, ALLOWED.options);
  if (!Object.keys(updates).length) throw new ValidationError("No valid fields provided");

  const t = await sequelize.transaction();
  try {
    const trade = await BinaryTrade.findOne({
      where: { id: tradeId },
      transaction: t,
      lock: t.LOCK.UPDATE,
    });
    if (!trade) throw new NotFoundError("Binary option trade not found");

    const userId = trade.userId;
    const prevProfit = Number(trade.profit || 0);
    await trade.update(updates, { transaction: t });

    if (updateBalance) {
      if (trade.walletType === "real") {
        const balance = await UserBalance.findOne({
          where: { userId },
          transaction: t,
          lock: t.LOCK.UPDATE,
        });
        if (!balance) throw new NotFoundError("User balance not found");

        const newProfit = Number(updates.profit ?? prevProfit);
        const delta = newProfit - prevProfit;
        if (delta !== 0) {
          await balance.update(
            { balance: Number(balance.balance) + delta },
            { transaction: t }
          );
        }
      }
    }

    await t.commit();
    return { trade };
  } catch (e) {
    await t.rollback();
    throw e;
  }
}

// services/adminUserTradingService.js  (close section)

// ═════════════════════════════════════════════════════════════════════════════
// 1. CLOSE DERIVATIVE POSITION
// ═════════════════════════════════════════════════════════════════════════════
async adminCloseDerivativePosition(positionId, { closeType, price, profit, entry_price }) {
  return sequelize.transaction(async (transaction) => {

    // 1. Fetch position — derive userId from record
    const position = await DerivativePosition.findOne({
      where: { id: positionId, status: "open" },
      lock: transaction.LOCK.UPDATE,
      transaction,
    });
    if (!position) throw new NotFoundError("Open derivative position not found.");

    const userId = position.userId;

    // 2. Fetch account
    const account = await DerivativeAccount.findOne({
      where: { id: position.account_id },
      lock: transaction.LOCK.UPDATE,
      transaction,
    });
    if (!account) throw new NotFoundError("Derivative account not found.");

    // 3. Fetch market
    const market = await MarketPair.findByPk(position.market_id, { transaction });
    if (!market) throw new NotFoundError("Market not found.");

    // 4. Resolve exit price
    let exitPrice;
    if (closeType === "auto") {
      const forexPrice = forexData.getTicker(market.symbol);
      if (!forexPrice) throw new Error(`No live price for ${market.symbol?.replace("_", "/")}`);
      exitPrice = position.side === "long"
        ? (forexPrice.bid ?? 0)
        : (forexPrice.ask ?? 0);
      if (!exitPrice || exitPrice <= 0) throw new Error("Invalid exit price.");
    } else {
      // manual — use provided price as exit price
      if (!price || price <= 0) throw new ValidationError("A valid price is required for manual close.");
      exitPrice = price;
    }

    // 5. Resolve entry price — allow override on manual
    const entryPrice = (closeType === "manual" && entry_price > 0)
      ? entry_price
      : toNum(position.entry_price);

    const qty      = toNum(position.qty);
    const leverage = position.leverage || 1;

    // 6. Resolve realized PnL
    let realizedPnl;
    if (closeType === "manual" && profit !== 0) {
      // Admin explicitly set the profit figure
      realizedPnl = round12(profit);
    } else {
      const rawPnl = position.side === "long"
        ? (exitPrice - entryPrice) * qty
        : (entryPrice - exitPrice) * qty;
      realizedPnl = round12(rawPnl);
    }

    // 7. Release margin
    const marginReleased = round12((entryPrice * qty) / leverage);

    // 8. Recompute account
    const prevBalance    = toNum(account.balance);
    const prevMarginUsed = toNum(account.margin_used);

    const newBalance    = round12(prevBalance + realizedPnl);
    const newMarginUsed = round12(Math.max(0, prevMarginUsed - marginReleased));

    const accountPreview = computeMarginFields({
      balance:        newBalance,
      unrealizedPnl:  0,
      marginUsed:     newMarginUsed,
    });

    // 9. Close position
    await position.update(
      {
        status:        "closed",
        entry_price:   entryPrice,          // updated if manual override
        mark_price:    exitPrice,
        realized_pnl:  realizedPnl,
        unrealized_pnl: 0,
        closed_at:     new Date(),
      },
      { transaction }
    );

    // 10. Update account
    await account.update(
      {
        balance:       newBalance,
        equity:        accountPreview.equity,
        margin_used:   newMarginUsed,
        margin_free:   accountPreview.margin_free,
        margin_level:  accountPreview.margin_level,
      },
      { transaction }
    );

    return {
      message: `${market.symbol} ${position.side} position closed`,
      data: {
        position: {
          id:           position.id,
          symbol:       market.symbol,
          side:         position.side,
          qty,
          entry_price:  entryPrice,
          exit_price:   exitPrice,
          realized_pnl: realizedPnl,
          leverage,
          status:       "closed",
          closed_at:    new Date().toISOString(),
        },
        account: {
          balance: newBalance,
          equity:  accountPreview.equity,
          free:    accountPreview.margin_free,
          used:    newMarginUsed,
          level:   accountPreview.margin_level,
        },
      },
    };
  });
}

// ═════════════════════════════════════════════════════════════════════════════
// 2. CLOSE TERMINAL TRADE
// ═════════════════════════════════════════════════════════════════════════════
async adminCloseTerminalTrade(tradeId, { closeType, price, profit, entry_price }) {
  if (!tradeId) throw new ValidationError("tradeId cannot be empty.");
  const t = await sequelize.transaction();
  try {

    // 1. Fetch trade — derive userId from record
    const trade = await TradeHistory.findOne({
      where: { id: tradeId, status: "open" },
      lock: t.LOCK.UPDATE,
      transaction: t,
    });
    if (!trade) throw new NotFoundError("Open trade not found.");

    const userId = trade.userId;

    // 2. Fetch market
    const market = await MarketPair.findByPk(trade.pairId, { transaction: t });
    if (!market) throw new NotFoundError("Market pair not found.");

    // 3. Resolve exit price
    let exitPrice;
    if (closeType === "auto") {
      const ticker = this.findTicker(market.symbol);
      if (!ticker) throw new Error(`No live price for ${market.symbol?.replace("_", "/")}`);
      exitPrice = trade.side === "buy"
        ? (ticker.bid ?? 0)
        : (ticker.ask ?? 0);
      if (!exitPrice || exitPrice <= 0) throw new Error("Invalid exit price.");
    } else {
      if (!price || price <= 0) throw new ValidationError("A valid price is required for manual close.");
      exitPrice = price;
    }

    // 4. Resolve entry price — allow override on manual
    const entryPrice = (closeType === "manual" && entry_price > 0)
      ? entry_price
      : Number(trade.entryPrice);

    const volume   = Number(trade.volume);
    const leverage = Number(trade.leverage) || 1;

    // 5. Resolve PnL
    let profitAmount;
    if (closeType === "manual" && profit !== 0) {
      profitAmount = parseFloat(Number(profit).toFixed(10));
    } else {
      const rawPnl = trade.side === "buy"
        ? (exitPrice - entryPrice) * volume
        : (entryPrice - exitPrice) * volume;
      profitAmount = parseFloat(rawPnl.toFixed(10));
    }

    const result = profitAmount > 0 ? "won" : profitAmount < 0 ? "loss" : "none";

    // 6. Update user balance
    const balance = await UserBalance.findOne({
      where: { userId },
      lock: t.LOCK.UPDATE,
      transaction: t,
    });
    if (!balance) throw new ConflictError("User wallet not found.");

    const currentBalance = Number(balance.balance ?? 0);
    const payout         = volume + profitAmount;
    balance.balance      = parseFloat((currentBalance + Math.max(0, payout)).toFixed(10));
    await balance.save({ transaction: t });

    // 7. Close trade — update entry_price if manually overridden
    await trade.update(
      {
        status:       "closed",
        result,
        entryPrice:   entryPrice,           // updated if manual override
        profitAmount: Math.abs(profitAmount),
        closedAt:     new Date(),
      },
      { transaction: t }
    );

    // 8. Audit transaction
    await Transaction.create(
      {
        transactionId: generateTransactionId("CL"),
        userId,
        source:        "trading",
        kind:          "trade_close",
        direction:     profitAmount >= 0 ? "credit" : "debit",
        toType:        "internal",
        currency:      "USD",
        amount:        Math.abs(profitAmount),
        status:        "completed",
        description:   `Closed ${trade.side.toUpperCase()} ${volume} ${market.symbol?.replace("_", "/")} @ ${exitPrice}`,
        metadata: {
          tradeId:      trade.id,
          orderId:      trade.orderId,
          symbol:       market.symbol,
          side:         trade.side,
          entryPrice,
          exitPrice,
          volume,
          leverage,
          profitAmount,
          result,
          closeType,
        },
      },
      { transaction: t }
    );

    await t.commit();

    return {
      message: `${market.symbol} ${trade.side} trade closed`,
      data: {
        trade: {
          id:          trade.id,
          orderId:     trade.orderId,
          symbol:      market.symbol,
          side:        trade.side,
          volume,
          entryPrice,
          exitPrice,
          profitAmount,
          result,
          status:      "closed",
          closedAt:    new Date().toISOString(),
        },
        account: {
          balance: balance.balance,
        },
      },
    };
  } catch (e) {
    await t.rollback();
    throw e;
  }
}

// ═════════════════════════════════════════════════════════════════════════════
// 3. SETTLE BINARY OPTION
// ═════════════════════════════════════════════════════════════════════════════
async adminSettleBinaryOption(tradeId, { closeType, price, profit }) {
  const transaction = await sequelize.transaction();
  try {

    // 1. Fetch trade — derive userId from record
    const trade = await BinaryTrade.findByPk(tradeId, {
      transaction,
      lock: transaction.LOCK.UPDATE,
    });
    if (!trade) throw new NotFoundError("Trade not found.");
    if (trade.status !== "open") throw new BadRequestError("Trade is not open.");

    const userId = trade.userId;

    // 2. Fetch pair
    const pair = await MarketPair.findOne({ where: { id: trade.assetId } });
    if (!pair) throw new NotFoundError("Trading pair not found.");

    // 3. Fetch balance
    const balance = await UserBalance.findOne({
      where: { userId },
      transaction,
      lock: transaction.LOCK.UPDATE,
    });
    if (!balance) throw new NotFoundError("Balance not found.");

    // 4. Resolve result price
    let resultPrice;
    if (closeType === "auto") {
      const latest = this.findTicker(pair.symbol);
      if (!latest) throw new Error(`No live price for ${pair.symbol?.replace("_", "/")}`);
      resultPrice = round6(latest.price);
    } else {
      if (!price || price <= 0) throw new ValidationError("A valid price is required for manual close.");
      resultPrice = round6(price);
    }

    // 5. Resolve status — auto uses direction logic, manual uses provided profit
    let status;
    let payoutAmount = 0;
    let finalProfit  = 0;

    if (closeType === "manual" && profit !== 0) {
      // Admin sets profit directly → derive status from profit sign
      finalProfit  = round4(profit);
      payoutAmount = round4(Number(trade.investment) + finalProfit);
      status       = finalProfit > 0 ? "won" : finalProfit < 0 ? "lost" : "draw";
    } else {
      // Auto — use direction vs price comparison
      status = this.resolveTradeStatus(trade.direction, trade.entryPrice, resultPrice);

      if (status === "won") {
        payoutAmount = round4(trade.potentialReturn);
        finalProfit  = round4(payoutAmount - trade.investment);
      } else if (status === "draw") {
        payoutAmount = round4(trade.investment);
        finalProfit  = 0;
      }
      // lost → payoutAmount & profit stay 0
    }

    // 6. Credit balance if won or draw
    if (status === "won" || status === "draw") {
      await balance.update(
        { balance: Number(balance.balance || 0) + payoutAmount },
        { transaction }
      );
    }

    // 7. Settle trade
    trade.resultPrice = resultPrice;
    trade.status      = status;
    trade.profit      = finalProfit;
    trade.payoutAmount = payoutAmount;
    trade.settledAt   = new Date();
    await trade.save({ transaction });

    await transaction.commit();

    // 8. Return open deals
    const openDeals = await BinaryTrade.findAll({
      where: { userId, status: "open" },
      include: [{ model: MarketPair, as: "pair" }],
      order: [["createdAt", "desc"]],
    });

    return {
      trade,
      openPositions: openDeals.map((d) => {
        const dateObj = new Date(d.createdAt);
        return {
          id:        d.id,
          date:      dateObj.toISOString().split("T")[0],
          time:      dateObj.toISOString().split("T")[1].slice(0, 8),
          asset:     d.pair?.name,
          symbol:    d.pair?.symbol,
          profit:    d.potentialReturn,
          duration:  d.durationSeconds,
          direction: d.direction,
          entry:     d.entryPrice,
          amount:    d.investment,
          type:      d.status,
          expires:   d.expiresAt,
          createdAt: d.createdAt,
        };
      }),
    };
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
}

  async allTradingData(query = {}) {

    const pairs = await MarketPair.findAll({ });
    const providers = await SignalProvider.findAll({});
    const copytraders = await TraderProfile.findAll({})

    return { pairs, providers, copytraders };
  }

}

module.exports = new adminUserTradingService();