// services/PlaceTradeService.js
const {
    sequelize, MarketPair, Asset, User, UserBalance, AssetHolding, AssetLot,
    Transaction, TradeHistory, BinaryTrade, DerivativePosition, DerivativeAccount,
    DerivativeOrder, SpotOrder, SymbolStats, CopyRelation, SignalSubscription, BotSubscription,
    Wallet
} = require("../../models");
const { Op } = require("sequelize");
const { generateTransactionId, getUploadedFile } = require("../../utils/helpers");
const { ValidationError, ConflictError, BadRequestError, NotFoundError } = require("../../utils/errors");
//const stockData = require("./stockDataService");
const logger = require("../../utils/logger");
const stockMarketData = require("../stockMarketData");
const forexMarketData = require("../forexMarketData");
const cryptoMarketData = require("../cryptoMarketData");

class PlaceTradeService {

    findTicker(symbol) {
        const sym = String(symbol ?? "").toUpperCase();
        return (
            forexMarketData.getTicker(sym) ??
            stockMarketData.getTicker(sym) ??
            cryptoMarketData.getTicker(sym) ??
            null
        );
    }
    // ─────────────────────────────────────────────────────────────────────────────
    // SHARED HELPERS
    // ─────────────────────────────────────────────────────────────────────────────

    /**
     * Resolve a user from either numeric id or email string.
     * The admin frontend sends whichever the admin typed in the Email field,
     * or the userId from the URL param — both are supported.
     */
    async _resolveUser(idOrEmail) {
        if (!idOrEmail) throw new ValidationError("User id or email is required");

        const isNumeric = /^\d+$/.test(String(idOrEmail).trim());
        const where = isNumeric
            ? { id: Number(idOrEmail) }
            : { email: String(idOrEmail).trim().toLowerCase() };

        const user = await User.findOne({ where });
        if (!user) throw new NotFoundError(`User not found: ${idOrEmail}`);
        return user;
    }

    /**
     * Validate that the user has the required relationship for the given source,
     * then return the resolved { tradedBy, traderId, signalId, botId } fields
     * that map directly onto TradeHistory / AssetLot columns.
     *
     * source  : "manual" | "auto" | "copy" | "signal" | "bot"
     * sourceId: id of the trader-profile / signal / bot (required for copy/signal/bot)
     */
    async _resolveSource(userId, source, sourceId) {
        const result = {
            tradedBy: "manual",   // TradeHistory.tradedBy default
            traderId: null,
            signalId: null,
            botId: null,
        };

        if (!source || source === "manual") {
            result.tradedBy = "manual";
            return result;
        }

        if (source === "auto") {
            result.tradedBy = "auto";
            return result;
        }

        // ── copy ──────────────────────────────────────────────────────────────────
        if (source === "copy") {
            if (!sourceId) throw new BadRequestError("sourceId (traderProfileId) is required for copy trades");
            const rel = await CopyRelation.findOne({
                where: { copierId: userId, traderProfileId: sourceId },
            });
            if (!rel) throw new ConflictError("No active copy relationship with this trader");
            result.tradedBy = "copy";
            result.traderId = sourceId;
            return result;
        }

        // ── signal ────────────────────────────────────────────────────────────────
        if (source === "signal") {
            if (!sourceId) throw new BadRequestError("sourceId (signalId) is required for signal trades");
            const sub = await SignalSubscription.findOne({
                where: { userId, signalId: sourceId, status: "active" },
            });
            if (!sub) throw new ConflictError("No active signal subscription for this user");
            result.tradedBy = "signal";
            result.signalId = sourceId;
            return result;
        }

        // ── bot ───────────────────────────────────────────────────────────────────
        if (source === "bot") {
            if (!sourceId) throw new BadRequestError("sourceId (botId) is required for bot trades");
            const sub = await BotSubscription.findOne({
                where: { userId, botId: sourceId, status: "active" },
            });
            if (!sub) throw new ConflictError("No active bot subscription for this user");
            result.tradedBy = "bot";
            result.botId = sourceId;
            return result;
        }

        throw new BadRequestError(`Unknown source type: ${source}`);
    }

    _round4(n) { return Math.round(Number(n) * 10_000) / 10_000; }
    _round6(n) { return Math.round(Number(n) * 1_000_000) / 1_000_000; }

    _computeTradeMath(amount, payoutPercent) {
        const potentialProfit = this._round4((amount * payoutPercent) / 100);
        const potentialReturn = this._round4(amount + potentialProfit);
        return { potentialProfit, potentialReturn };
    }

    async _mutateWalletWithLedger({ transaction, userId, walletType, delta, ledgerType, note }) {
        const balance = await UserBalance.findOne({ where: { userId }, transaction, lock: transaction.LOCK.UPDATE });
        if (!balance) throw new ConflictError("Balance record not found");
        const field = walletType === "demo" ? "demoBalance" : "balance";
        const current = Number(balance[field] ?? 0);
        if (current + delta < 0) throw new BadRequestError("Insufficient balance");
        balance[field] = this._round4(current + delta);
        await balance.save({ transaction });
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // 1. ASSET HOLDING TRADE
    // ─────────────────────────────────────────────────────────────────────────────
    async tradeAsset(data) {
        const { email: userRef, asset: symbol, side, price, marketType, limit, tradeType, quantity, source, sourceId } = data;

        // ── resolve user ──────────────────────────────────────────────────────────
        const user = await this._resolveUser(userRef);
        const userId = user.id;
        const currency = user?.email;

        // ── validate source relationship ──────────────────────────────────────────
        const { tradedBy, traderId, signalId, botId } = await this._resolveSource(userId, source, sourceId);

        const qty = Number(quantity ?? 0);

        if (!qty || qty <= 0) throw new ValidationError("Invalid quantity");


        const pair = await MarketPair.findOne({ where: { symbol, status: "active" } });
        if (!pair) throw new ConflictError(`${symbol} not available for trading`);


        let px;
        if (tradeType === "auto") {
            const ticker = stockMarketData.getTicker(pair.symbol);
            if (!ticker) throw new Error(`No live price for ${pair.symbol?.replace("_", "/")}`);
            px = ticker.price;
            if (!px || px <= 0) throw new Error("Invalid exit price.");
        } else {
            // manual — use provided price as exit price
            const pxr = Number(price ?? 0);
            if (!pxr || pxr <= 0) throw new ValidationError("A valid price is required for manual trade.");
            px = pxr;
        }

        const lotCost = px * qty;

        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 balance not found");
            const currentBalance = Number(balance.balance || 0);

            let holding = await AssetHolding.findOne({ where: { userId, assetId: pair.id }, transaction: t, lock: t.LOCK.UPDATE });

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

                if (holding) {
                    const newQty = Number(holding.quantity || 0) + qty;
                    const newCost = Number(holding.totalCost || 0) + lotCost;
                    await holding.update({ quantity: newQty, totalCost: newCost, avgBuyPrice: newCost / newQty }, { transaction: t });
                } else {
                    holding = await AssetHolding.create(
                        { userId, assetId: pair.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("User does not own this asset");
                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 });
            }

            const assetLot = await AssetLot.create(
                {
                    userId,
                    assetId: pair.id,
                    quantity: qty,
                    buyPrice: px,
                    side,
                    type: marketType,
                    tradedBy,
                    traderId,
                    signalId,
                    botId,
                    marketLimit: limit ?? null,
                    status: "closed",
                },
                { transaction: t }
            );

            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} [${tradedBy}]`,
                    assetLotId: assetLot.id,
                    amount: lotCost,
                    netAmount: lotCost,
                    status: "completed",
                },
                { transaction: t }
            );

            await t.commit();

            const [holdings, lots] = await Promise.all([
                AssetHolding.findAll({
                    where: { userId }, order: [["createdAt", "DESC"]], limit: 5,
                    include: [{ model: MarketPair, as: "asset", include: [{ model: Asset, as: "base" }, { model: SymbolStats, as: "rate" }] }],
                }),
                AssetLot.findAll({
                    where: { userId }, order: [["createdAt", "DESC"]], limit: 5,
                    include: [{ model: MarketPair, as: "pair", include: [{ model: Asset, as: "base" }] }],
                }),
            ]);

            return {
                transaction: tx,
                assetTrades: lots.map(l => ({
                    id: l.id,
                    symbol: l.pair?.symbol || "—",
                    logo: getUploadedFile(l.pair?.base?.logo, "assets") || null,
                    type: l.side === "buy" ? "Buy" : "Sell",
                    orderType: l.type.charAt(0).toUpperCase() + l.type.slice(1),
                    price: l.entryPrice || l.buyPrice,
                    shares: l.quantity || 0,
                    filled: l.status === "closed" ? l.quantity : 0,
                    status: l.status.charAt(0).toUpperCase() + l.status.slice(1),
                    tradedBy: l.tradedBy,
                    time: new Date(l.createdAt).toISOString().replace("T", " ").substring(0, 19),
                })),
                assetHoldings: holdings.map(h => ({
                    symbol: h.asset?.symbol || "—",
                    shares: h.quantity || 0,
                    avgCost: h.avgBuyPrice || 0,
                    logo: getUploadedFile(h.asset?.base?.logo, "assets") || 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) {
            logger.error("[tradeAsset]", e);
            await t.rollback();
            throw e;
        }
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // 2. TERMINAL TRADE
    // ─────────────────────────────────────────────────────────────────────────────
    async tradeTerminal(data) {
        const { email: userRef, asset, side, marketType = "market", tradeType, limit, quantity, price, leverage, duration, stopLoss, takeProfit, source, sourceId } = data;

        const user = await this._resolveUser(userRef);
        const userId = user.id;
        const currency = user.currency

        const { tradedBy, traderId, signalId, botId } = await this._resolveSource(userId, source, sourceId);

        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);
        const lev = Number(leverage);
        const dur = Number(duration);


        if (!Number.isFinite(qty) || qty <= 0) throw new BadRequestError("quantity must be > 0");
        if (!Number.isFinite(lev) || lev <= 0) throw new BadRequestError("leverage must be > 0");
        if (!Number.isFinite(dur) || dur <= 0) throw new BadRequestError("duration must be > 0");
        if (marketType === "limit" && (!Number.isFinite(Number(limit)) || Number(limit) <= 0)) {
            throw new BadRequestError("limit price is required for limit orders");
        }

        const assetRow = await MarketPair.findOne({ where: { symbol: asset, status: "active" } });
        if (!assetRow) throw new NotFoundError("Trading pair not found or not tradable");

        let px;
        if (tradeType === "auto") {
            const ticker = this.findTicker(assetRow.symbol);
            if (!ticker) throw new Error(`No live price for ${assetRow.symbol?.replace("_", "/")}`);
            px = ticker.price;
            if (!px || px <= 0) throw new Error("Invalid exit price.");
        } else {
            // manual — use provided price as exit price
            const pxr = Number(price ?? 0);
            if (!pxr || pxr <= 0) throw new ValidationError("A valid price is required for manual trade.");
            px = pxr;
        }


        // Anti-spam: block duplicate pending within 30s
        const existing = await TradeHistory.findOne({
            where: {
                userId, pairId: assetRow.id, side,
                status: { [Op.in]: ["pending", "open", "initiated", "processing"] },
                createdAt: { [Op.gte]: new Date(Date.now() - 30_000) },
            },
        });
        if (existing) throw new BadRequestError("Duplicate pending trade detected. Please wait 30s.");



        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 balance not found");
            const currentBalance = Number(balance.balance ?? 0);
            if (currentBalance < qty) throw new BadRequestError("Insufficient balance");

            balance.balance = currentBalance - qty;
            await balance.save({ transaction: t });

            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.replace("_", "/")} @ ${px} (x${lev}) [${tradedBy}]`,
                    metadata: { asset, side, marketType, limit: marketType === "limit" ? Number(limit) : null, quantity: qty, price: px, leverage: lev, duration: dur },
                },
                { transaction: t }
            );

            const trade = await TradeHistory.create(
                {
                    orderId: tx.transactionId,
                    userId,
                    pairId: assetRow.id,
                    side,
                    orderType: marketType,
                    volume: qty,
                    entryPrice: px,
                    requestedPrice: limit ?? null,
                    leverage: lev,
                    duration: dur,
                    takeProfit: takeProfit ?? null,
                    stopLoss: stopLoss ?? null,
                    tradedBy,
                    traderId,
                    signalId,
                    status: "open",
                },
                { transaction: t }
            );

            await t.commit();
            return { tx, trade };
        } catch (e) {
            logger.error("[tradeTerminal]", e);
            await t.rollback();
            throw e;
        }
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // 3. FOREX TRADE
    // ─────────────────────────────────────────────────────────────────────────────
    async tradeForex(data) {
        const { email: userRef, asset: symbol, side, marketType = "market", leverage, lot, tradeType, limit, stopLoss, takeProfit, quantity, price, source, sourceId } = data;

        const user = await this._resolveUser(userRef);
        const userId = user.id;
        const currency = user.currency
        const { tradedBy, traderId, signalId, botId } = await this._resolveSource(userId, source, sourceId);

        if (!symbol) throw new BadRequestError("asset is required");
        if (!["long", "short"].includes(side)) throw new BadRequestError("side must be long or short");

        const lot_ = Number(lot);
        const lev = Number(leverage);

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

        const pair = await MarketPair.findOne({ where: { symbol, status: "active" } });
        if (!pair) throw new NotFoundError("Pair not found or not active");

        // Bug 3 fix: declare pxr at outer scope so it's accessible in DerivativeOrder.create
        let px;
        let pxr;
        if (tradeType === "auto") {
            // Bug 1 fix: use pair.symbol, not assetRow.symbol
            const ticker = this.findTicker(pair.symbol);
            if (!ticker) throw new Error(`No live price for ${pair.symbol?.replace("_", "/")}`);
            px = side === 'long' ? ticker.bid : ticker.ask;
            if (!px || px <= 0) throw new Error("Invalid exit price.");
        } else {
            pxr = Number(price ?? 0);
            if (!pxr || pxr <= 0) throw new ValidationError("A valid price is required for manual trade.");
            px = pxr;
        }

        const contractSize = Number(pair.contractSize ?? 100_000);
        const marginRequired = (lot_ * contractSize * px) / lev;

        let account = await DerivativeAccount.findOne({ where: { userId, currency, status: "active" } });
        if (!account) throw new NotFoundError("No active derivative account. Please create one first.");

        if (Number(account.balance ?? 0) < marginRequired) {
            throw new BadRequestError(`Insufficient margin. Required: ${marginRequired.toFixed(2)}`);
        }

        const t = await sequelize.transaction();
        try {
            account = await DerivativeAccount.findOne({ where: { id: account.id }, transaction: t, lock: t.LOCK.UPDATE });

            account.balance = Number(account.balance) - marginRequired;
            account.usedMargin = Number(account.usedMargin ?? 0) + marginRequired;
            await account.save({ transaction: t });

            const order = await DerivativeOrder.create(
                {
                    userId,
                    accountId: account.id,
                    marketId: pair.id,
                    // Bug 2 fix: side is already "long"/"short", not "buy"/"sell"
                    side,
                    orderType: marketType,
                    qty: lot_,
                    leverage: lev,
                    requestedPrice: pxr ?? null,
                    entryPrice: px,
                    stopLoss: stopLoss ?? null,
                    takeProfit: takeProfit ?? null,
                    limitPrice: marketType === "limit" ? Number(limit) : null,
                    tradedBy,
                    traderId,
                    signalId,
                    botId,
                    status: "filled",
                    filledAt: new Date(),
                },
                { transaction: t }
            );

            const position = await DerivativePosition.create(
                {
                    userId,
                    account_id: account.id,
                    market_id: pair.id,
                    order_id: order.id,
                    // Bug 2 fix: same as above
                    side,
                    leverage: lev,
                    position_mode: "cross",
                    qty: lot_,
                    entry_price: px,
                    mark_price: px,
                    unrealized_pnl: 0,
                    realized_pnl: 0,
                    stop_loss: stopLoss ?? null,
                    take_profit: takeProfit ?? null,
                    status: "open",
                    opened_at: new Date(),
                },
                { transaction: t }
            );

            await DerivativeTrade.create(
                {
                    userId,
                    market_id: pair.id,
                    maker_order_id: null,
                    taker_order_id: order.id,
                    price: px,
                    // Bug 5 fix: use lot_ (qty is undefined; quantity is the raw destructured value)
                    qty: lot_,
                    maker_account_id: null,
                    taker_account_id: account.id,
                    fee: 0,
                    fee_asset_id: null,
                },
                // Bug 4 fix: was { transaction } (undefined), must be { transaction: t }
                { transaction: t }
            );

            const tx = await Transaction.create(
                {
                    transactionId: generateTransactionId("FX"),
                    userId,
                    source: "trading",
                    kind: "forex_margin",
                    direction: "debit",
                    toType: "internal",
                    currency,
                    amount: marginRequired,
                    netAmount: marginRequired,
                    status: "completed",
                    description: `Forex ${side.toUpperCase()} ${lot_} lot ${symbol} @ ${px} (x${lev}) [${tradedBy}]`,
                },
                { transaction: t }
            );

            await t.commit();
            return { tx, order, position };
        } catch (e) {
            logger.error("[tradeForex]", e);
            await t.rollback();
            throw e;
        }
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // 4. SPOT TRADE
    // ─────────────────────────────────────────────────────────────────────────────
    async tradeSpot(data) {
        const {
            email: userRef,
            symbol,
            side,
            apiOrderType = "market",
            tradeType,
            price,
            stopPrice,
            amount,     // ← always BASE asset (BTC) — how many BTC to buy or sell
            source,
            sourceId,
        } = data;

        // ── 1. Resolve user ───────────────────────────────────────────────────────
        const user = await this._resolveUser(userRef);
        const userId = user.id;
        const currency = user.currency;

        // ── 2. Resolve source relationship ────────────────────────────────────────
        const { tradedBy, traderId, signalId, botId } = await this._resolveSource(userId, source, sourceId);

        // ── 3. Basic validation ───────────────────────────────────────────────────
        if (!symbol) throw new BadRequestError("symbol is required");
        if (!["buy", "sell"].includes(side)) throw new BadRequestError("side must be buy or sell");

        // amount = BTC quantity (base asset)
        const baseAmt = Number(amount);
        if (!Number.isFinite(baseAmt) || baseAmt <= 0) throw new BadRequestError("amount (base quantity) must be > 0");

        if (apiOrderType === "stop") {
            const sp = Number(stopPrice);
            if (!Number.isFinite(sp) || sp <= 0) throw new BadRequestError("stopPrice is required for stop orders");
        }

        // ── 4. Load pair with base + quote ────────────────────────────────────────
        // BTC/ETH → base = BTC, quote = ETH
        const pair = await MarketPair.findOne({
            where: { symbol, status: "active" },
            include: [
                { model: Asset, as: "base", required: true },   // BTC
                { model: Asset, as: "quote", required: true },   // ETH
            ],
        });
        if (!pair) throw new NotFoundError("Pair not found or not active");

        // ── 5. Resolve price (px = how much ETH per 1 BTC) ───────────────────────
        let px;
        if (tradeType === "auto") {
            const ticker = this.findTicker(symbol);
            if (!ticker?.price || ticker.price <= 0) {
                throw new BadRequestError(`No live price available for ${symbol}`);
            }
            px = Number(ticker.price);
        } else {
            // manual — admin supplies exact price
            const pxr = Number(price ?? 0);
            if (!pxr || pxr <= 0) throw new ValidationError("A valid price is required for manual trade type");
            px = pxr;
        }

        // ── 6. Compute ETH amounts from BTC quantity ──────────────────────────────
        //
        //   amount  = BTC (base)  — entered by admin
        //   px      = ETH per BTC — from ticker or manual
        //
        //   BUY  2 BTC @ 15:  spend 2 * 15 = 30 ETH,  receive 2 BTC
        //   SELL 2 BTC @ 15:  spend 2 BTC,             receive 2 * 15 = 30 ETH
        //
        const quoteCost = side === "buy" ? baseAmt * px : 0;    // ETH going OUT on buy
        const quoteReturn = side === "sell" ? baseAmt * px : 0;    // ETH coming IN on sell

        // ── 7. Open DB transaction ────────────────────────────────────────────────
        const t = await sequelize.transaction();
        try {
            // Lock both wallets upfront
            const baseWallet = await Wallet.findOne({
                where: { userId, coinId: pair.base?.id },
                transaction: t,
                lock: t.LOCK.UPDATE,
            });
            const quoteWallet = await Wallet.findOne({
                where: { userId, coinId: pair.quote?.id },
                transaction: t,
                lock: t.LOCK.UPDATE,
            });

            if (!baseWallet) throw new ConflictError(`No ${pair.base?.symbol}  wallet found for this user`);
            if (!quoteWallet) throw new ConflictError(`No ${pair.quote?.symbol} wallet found for this user`);

            const baseBalance = Number(baseWallet.balance ?? 0);
            const quoteBalance = Number(quoteWallet.balance ?? 0);

            // ── BUY: user pays ETH, receives BTC ─────────────────────────────────
            if (side === "buy") {
                if (quoteBalance < quoteCost) {
                    throw new BadRequestError(
                        `Insufficient ${pair.quote?.symbol}. ` +
                        `Need ${quoteCost.toFixed(8)}, have ${quoteBalance.toFixed(8)}`
                    );
                }

                // ETH leaves quote wallet
                await quoteWallet.update(
                    { balance: quoteBalance - quoteCost },
                    { transaction: t }
                );

                // BTC enters base wallet
                await baseWallet.update(
                    { balance: baseBalance + baseAmt },
                    { transaction: t }
                );
            }

            // ── SELL: user pays BTC, receives ETH ────────────────────────────────
            if (side === "sell") {
                if (baseBalance < baseAmt) {
                    throw new BadRequestError(
                        `Insufficient ${pair.base?.symbol}. ` +
                        `Need ${baseAmt.toFixed(8)}, have ${baseBalance.toFixed(8)}`
                    );
                }

                // BTC leaves base wallet
                await baseWallet.update(
                    { balance: baseBalance - baseAmt },
                    { transaction: t }
                );

                // ETH enters quote wallet
                await quoteWallet.update(
                    { balance: quoteBalance + quoteReturn },
                    { transaction: t }
                );
            }

            // ── 8. Spot order record ──────────────────────────────────────────────
            const order = await SpotOrder.create(
                {
                    user_id: userId,
                    market_id: pair.id,
                    side,
                    type: apiOrderType,
                    price: px,
                    stop_price: apiOrderType === "stop" ? Number(stopPrice) : null,
                    qty: baseAmt,    // always stored as BTC (base)
                    qty_filled: side === "buy" ? quoteCost : quoteReturn,  // ETH equivalent
                    tradedBy,
                    traderId,
                    signalId,
                    botId,
                    status: apiOrderType === "market" ? "filled" : "open",
                    filledAt: apiOrderType === "market" ? new Date() : null,
                },
                { transaction: t }
            );

            // ── 9. Two transaction records (one per side of the trade) ────────────
            const txBase = await Transaction.create(
                {
                    transactionId: generateTransactionId("SP"),
                    userId,
                    source: "wallet",
                    kind: side === "buy" ? "spot_buy" : "spot_sell",
                    direction: side === "buy" ? "credit" : "debit",  // BTC: credit on buy, debit on sell
                    toType: "internal",
                    currency: pair.base?.symbol,   // BTC
                    amount: baseAmt,
                    netAmount: baseAmt,
                    status: "completed",
                    description: `Spot ${side}: ${side === "buy" ? "received" : "sent"} ${baseAmt.toFixed(8)} ${pair.base?.symbol} [${tradedBy}]`,
                },
                { transaction: t }
            );

            const txQuote = await Transaction.create(
                {
                    transactionId: generateTransactionId("SP"),
                    userId,
                    source: "wallet",
                    kind: side === "buy" ? "spot_buy" : "spot_sell",
                    direction: side === "buy" ? "debit" : "credit",  // ETH: debit on buy, credit on sell
                    toType: "internal",
                    currency: pair.quote?.symbol,  // ETH
                    amount: side === "buy" ? quoteCost : quoteReturn,
                    netAmount: side === "buy" ? quoteCost : quoteReturn,
                    status: "completed",
                    description: `Spot ${side}: ${side === "buy" ? "spent" : "received"} ${(side === "buy" ? quoteCost : quoteReturn).toFixed(8)} ${pair.quote?.symbol} [${tradedBy}]`,
                },
                { transaction: t }
            );

            await t.commit();
            return { order, transactions: [txBase, txQuote] };

        } catch (e) {
            logger.error("[tradeSpot]", e);
            await t.rollback();
            throw e;
        }
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // 5. BINARY / OPTIONS TRADE
    // ─────────────────────────────────────────────────────────────────────────────
    async tradeOption( data) {
        const {
            email: userRef,
            symbol,
            direction,
            investment,
            durationSeconds,
            walletType = "real",
            price,
            source,
            sourceId,
            status = "open", // open | closed
            result, // win | lose | draw
        } = data;

        const user = await this._resolveUser(userRef);
        const userId = user.id;

        const { tradedBy, traderId, signalId, botId } =
            await this._resolveSource(userId, source, sourceId);

        // ── Validation ───────────────────────────────────────────────────────
        if (!["buy", "sell"].includes(direction)) {
            throw new BadRequestError("direction must be buy or sell");
        }

        if (!["open", "closed"].includes(status)) {
            throw new BadRequestError("status must be open or closed");
        }

        if (
            status === "closed" &&
            !["win", "lose", "draw"].includes(result)
        ) {
            throw new BadRequestError(
                "Closed trades require result: win, lose or draw"
            );
        }

        const amount = this._round4(Number(investment));

        if (!amount || amount <= 0) {
            throw new BadRequestError("Investment must be greater than 0");
        }

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

        if (!asset) {
            throw new NotFoundError("Asset not found");
        }

        const entryPrice = this._round6(Number(price));

        if (!entryPrice || entryPrice <= 0) {
            throw new BadRequestError("Invalid entry price");
        }

        const payoutPercent = this._round4(
            Number(asset.payoutPercent ?? 63)
        );

        const { potentialProfit, potentialReturn } =
            this._computeTradeMath(amount, payoutPercent);

        // ── Balance Check ────────────────────────────────────────────────────
        const balance = await UserBalance.findOne({
            where: { userId },
        });

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

        const currentBalance =
            walletType === "real"
                ? Number(balance.balance || 0)
                : Number(balance.demoBalance || 0);

        if (status === "open" && currentBalance < amount) {
            throw new BadRequestError("Insufficient balance");
        }

        const transaction = await sequelize.transaction();

        try {
            const now = new Date();

            const expiresAt = new Date(
                now.getTime() + Number(durationSeconds) * 1000
            );

            let finalStatus = "open";
            let payoutAmount = 0;
            let profit = 0;
            let settledAt = null;

            // ── Handle Closed Trades ─────────────────────────────────────────
            if (status === "closed") {
                finalStatus =
                    result === "win"
                        ? "won"
                        : result === "lose"
                            ? "lost"
                            : "draw";

                settledAt = now;

                if (result === "win") {
                    payoutAmount = potentialReturn;
                    profit = potentialProfit;
                }

                if (result === "draw") {
                    payoutAmount = amount;
                    profit = 0;
                }

                if (result === "lose") {
                    payoutAmount = 0;
                    profit = -amount;
                }

                // update wallet only for win/draw
                if (["win", "draw"].includes(result)) {
                    const field =
                        walletType === "real"
                            ? "realBalance"
                            : "demoBalance";

                    await balance.increment(
                        { [field]: payoutAmount },
                        { transaction }
                    );
                }
            }

            // ── Create Trade ────────────────────────────────────────────────
            const trade = await BinaryTrade.create(
                {
                    userId,
                    walletType,

                    assetId: asset.id,
                    assetSymbol: asset.symbol,

                    direction,

                    investment: amount,

                    payoutPercent,

                    potentialProfit,
                    potentialReturn,

                    entryPrice,
                    entryPriceLockedAt: now,

                    durationSeconds,

                    expiresAt,

                    status: finalStatus,

                    resultPrice:
                        status === "closed" ? entryPrice : null,

                    settledAt,

                    payoutAmount,

                    profit,

                    tradedBy,
                    traderId,
                    signalId,
                    botId,

                    meta: {
                        assetName: asset.name,
                        sourcePrice: price,
                        result,
                    },
                },
                { transaction }
            );

            await transaction.commit();

            return {
                success: true,
                trade,
            };
        } catch (e) {
            await transaction.rollback();

            logger.error("[placeTrade]", e);

            throw e;
        }
    }
}

module.exports = new PlaceTradeService();