'use strict';

const { Op } = require('sequelize');
const forexMarketData = require('../services/forexMarketData');
const stockMarketData = require('../services/stockMarketData');
const cryptoMarketData = require('../services/cryptoMarketData');
// node-cron removed — sub-minute ticks use setInterval instead
const {
    DerivativePosition,
    TradeHistory,
    UserBalance,
    DerivativeAccount,
    BinaryTrade,
    MarketPair,
    Transaction,
    sequelize,
} = require('../models');
const { generateTransactionId } = require('../utils/helpers');

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

const n  = (v) => Number(v ?? 0);
const r  = (v, d = 10) => parseFloat(n(v).toFixed(d));
const r4 = (v) => r(v, 4);
const r6 = (v) => r(v, 6);
const r12 = (v) => r(v, 12);

function findTicker(symbol) {
    const sym = String(symbol ?? '').toUpperCase();
    return (
        forexMarketData.getTicker(sym) ??
        stockMarketData.getTicker(sym) ??
        cryptoMarketData.getTicker(sym) ??
        null
    );
}

function computeMarginFields({ balance, unrealizedPnl = 0, marginUsed }) {
    const equity      = r12(balance + unrealizedPnl);
    const marginFree  = r12(Math.max(0, equity - marginUsed));
    const marginLevel = marginUsed > 0 ? r12((equity / marginUsed) * 100) : 0;
    return { equity, margin_free: marginFree, margin_level: marginLevel };
}

// ─── Terminal (Spot/Leveraged) Trade Closer ──────────────────────────────────

async function closeTerminalTrade(trade, exitPrice, reason = 'manual') {
    const t = await sequelize.transaction();
    try {
        // Re-fetch with lock inside this transaction
        const locked = await TradeHistory.findOne({
            where: { id: trade.id, status: 'open' },
            lock: t.LOCK.UPDATE,
            transaction: t,
        });
        if (!locked) { await t.rollback(); return; }

        const market = await MarketPair.findByPk(locked.pairId, { transaction: t });
        if (!market) throw new Error('Market pair not found.');

        const entry     = n(locked.entryPrice);
        const volume    = n(locked.volume);
        const leverage  = n(locked.leverage) || 1;

        const rawPnl =
            locked.side === 'buy'
                ? (exitPrice - entry) * volume
                : (entry - exitPrice) * volume;

        const profitAmount = r(rawPnl, 10);
        const result       = profitAmount > 0 ? 'won' : profitAmount < 0 ? 'loss' : 'none';
        const payout       = volume + profitAmount;          // volume was the margin locked

        // Update balance — never go below 0
        const balance = await UserBalance.findOne({
            where: { userId: locked.userId },
            lock: t.LOCK.UPDATE,
            transaction: t,
        });
        if (!balance) throw new Error('User wallet not found.');

        balance.balance = r(n(balance.balance) + Math.max(0, payout), 10);
        await balance.save({ transaction: t });

        // Close trade
        await locked.update(
            {
                status:       'closed',
                result,
                profitAmount: Math.abs(profitAmount),
                closedAt:     new Date(),
            },
            { transaction: t }
        );

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

        await t.commit();
    } catch {
        await t.rollback().catch(() => {});
    }
}

// ─── Binary / Option Trade Settler ───────────────────────────────────────────

function resolveTradeStatus(direction, entryPrice, resultPrice) {
    const entry  = n(entryPrice);
    const result = n(resultPrice);
    if (result === entry) return 'draw';
    if (direction === 'buy')  return result > entry ? 'won' : 'lost';
    if (direction === 'sell') return result < entry ? 'won' : 'lost';
    throw new Error('Invalid direction');
}

async function settleOptionTrade(trade) {
    const transaction = await sequelize.transaction();
    try {
        const locked = await BinaryTrade.findOne({
            where: { id: trade.id, status: 'open' },
            lock: transaction.LOCK.UPDATE,
            transaction,
        });
        if (!locked) { await transaction.rollback(); return; }

        const pair = await MarketPair.findByPk(locked.assetId, { transaction });
        if (!pair) throw new Error('Trading pair not found.');

        // Get live price
        const ticker = findTicker(pair.symbol);
        if (!ticker) throw new Error(`No live price for ${pair.symbol}`);

        const resultPrice = r6(ticker.price ?? ticker.bid ?? ticker.ask);
        if (!resultPrice || resultPrice <= 0) throw new Error('Invalid result price.');

        const status = resolveTradeStatus(locked.direction, locked.entryPrice, resultPrice);

        let payoutAmount = 0;
        let profit       = 0;

        if (status === 'won') {
            payoutAmount = r4(locked.potentialReturn);
            profit       = r4(locked.potentialProfit);
        }

        // Update balance only when there's a payout
        if (payoutAmount > 0) {
            const balance = await UserBalance.findOne({
                where: { userId: locked.userId },
                lock: transaction.LOCK.UPDATE,
                transaction,
            });
            if (!balance) throw new Error('Balance not found.');

            balance.balance = r(n(balance.balance) + payoutAmount, 10);
            await balance.save({ transaction });

            await Transaction.create(
                {
                    transactionId: generateTransactionId('CL'),
                    userId:        locked.userId,
                    source:        'trading',
                    kind:          'trade_close',
                    direction:     'credit',
                    toType:        'internal',
                    currency:      'USD',
                    amount:        payoutAmount,
                    status:        'completed',
                    description:   `Option settled ${locked.direction.toUpperCase()} ${pair.symbol?.replace('_', '/')} — ${status}`,
                    metadata: {
                        tradeId:      locked.id,
                        symbol:       pair.symbol,
                        direction:    locked.direction,
                        entryPrice:   locked.entryPrice,
                        resultPrice,
                        payoutAmount,
                        profit,
                        status,
                    },
                },
                { transaction }
            );
        }

        locked.resultPrice  = resultPrice;
        locked.status       = status;
        locked.profit       = profit;
        locked.payoutAmount = payoutAmount;
        locked.settledAt    = new Date();
        await locked.save({ transaction });

        await transaction.commit();
    } catch {
        await transaction.rollback().catch(() => {});
    }
}

// ─── Derivative (Forex/CFD) Position Closer ──────────────────────────────────

async function closeDerivativePosition(position, exitPrice, reason = 'manual') {
    const t = await sequelize.transaction();
    try {
        const locked = await DerivativePosition.findOne({
            where: { id: position.id, status: 'open' },
            lock: t.LOCK.UPDATE,
            transaction: t,
        });
        if (!locked) { await t.rollback(); return; }

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

        const market = await MarketPair.findByPk(locked.market_id, { transaction: t });
        if (!market) throw new Error('Market pair not found.');

        const entry  = n(locked.entry_price);
        const qty    = n(locked.qty);
        const lev    = n(locked.leverage) || 1;

        const rawPnl =
            locked.side === 'long'
                ? (exitPrice - entry) * qty
                : (entry - exitPrice) * qty;

        const realizedPnl    = r12(rawPnl);
        const marginReleased = r12((entry * qty) / lev);

        const prevBalance    = n(account.balance);
        const prevMarginUsed = n(account.margin_used);

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

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

        // Close position
        await locked.update(
            {
                status:         reason === 'liquidation' ? 'liquidating' : 'closed',
                mark_price:     exitPrice,
                realized_pnl:   realizedPnl,
                unrealized_pnl: 0,
                closed_at:      new Date(),
            },
            { transaction: t }
        );

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

        await t.commit();
    } catch {
        await t.rollback().catch(() => {});
    }
}

// ─── Margin / Liquidation Check ───────────────────────────────────────────────
//
// After all tick-based closes, re-evaluate each user's derivative account.
// If margin_level drops below 100% (margin call threshold), forcibly close
// positions starting from the one with the largest loss until the account
// is back above the threshold or all positions are closed.

async function runMarginChecks() {
    // Fetch all accounts that have open positions
    const accounts = await DerivativeAccount.findAll({
        where: { status: { [Op.in]: ['active', 'restricted'] } },
        include: [
            {
                model: DerivativePosition,
                as: 'positions',          // adjust alias to match your associations
                where: { status: 'open' },
                required: true,
            },
        ],
    });

    for (const account of accounts) {
        try {
            // Recompute live unrealizedPnl across all open positions
            let totalUnrealizedPnl = 0;
            const positionsWithPnl = [];

            for (const pos of account.positions) {
                const market = await MarketPair.findByPk(pos.market_id);
                if (!market) continue;

                const ticker = findTicker(market.symbol);
                if (!ticker) continue;

                const markPrice = r12(
                    pos.side === 'long'
                        ? (ticker.bid ?? ticker.price ?? 0)
                        : (ticker.ask ?? ticker.price ?? 0)
                );
                const entry = n(pos.entry_price);
                const qty   = n(pos.qty);

                const unrealizedPnl = r12(
                    pos.side === 'long'
                        ? (markPrice - entry) * qty
                        : (entry - markPrice) * qty
                );

                totalUnrealizedPnl += unrealizedPnl;
                positionsWithPnl.push({ pos, markPrice, unrealizedPnl });
            }

            const balance    = n(account.balance);
            const marginUsed = n(account.margin_used);
            const equity     = r12(balance + totalUnrealizedPnl);
            const marginLevel = marginUsed > 0 ? r12((equity / marginUsed) * 100) : 999999;

            // Update mark-to-market fields on the account
            await account.update({
                equity,
                margin_free:  r12(Math.max(0, equity - marginUsed)),
                margin_level: marginLevel,
                status:       marginLevel < 100 ? 'restricted' : 'active',
            });

            // Also update unrealized_pnl & mark_price on each position
            for (const { pos, markPrice, unrealizedPnl } of positionsWithPnl) {
                await pos.update({ mark_price: markPrice, unrealized_pnl: unrealizedPnl });
            }

            // Liquidation: if margin_level < 100%, close worst positions first
            if (marginLevel < 100) {
                const sorted = [...positionsWithPnl].sort(
                    (a, b) => a.unrealizedPnl - b.unrealizedPnl
                );

                for (const { pos, markPrice } of sorted) {
                    await closeDerivativePosition(pos, markPrice, 'liquidation');

                    await account.reload();
                    const newMarginLevel = n(account.margin_level);
                    if (newMarginLevel >= 100) break;
                }
            }
        } catch {
            // silent
        }
    }
}

// ─── Main Cron (every 5 seconds) ─────────────────────────────────────────────

let running = false;

async function runTradingCron() {
    if (running) return;
    running = true;

    try {
        // ── Fetch all open trades in parallel ──────────────────────────────
        const [terminalTrades, forexPositions, optionTrades] = await Promise.all([
            TradeHistory.findAll({ where: { status: 'open' } }),
            DerivativePosition.findAll({ where: { status: 'open' } }),
            BinaryTrade.findAll({
                where: {
                    status:    'open',
                    expiresAt: { [Op.lte]: new Date() },   // only expired ones
                },
            }),
        ]);

        const now = Date.now();

        // ── 1. Terminal trades: stop-loss / take-profit / duration expiry ──
        await Promise.all(
            terminalTrades.map(async (trade) => {
                try {
                    const market = await MarketPair.findByPk(trade.pairId);
                    if (!market) return;

                    const ticker = findTicker(market.symbol);
                    if (!ticker) return;

                    const bid = n(ticker.bid ?? ticker.price);
                    const ask = n(ticker.ask ?? ticker.price);

                    // Buy: current value is bid; Sell: current value is ask
                    const currentPrice = trade.side === 'buy' ? bid : ask;
                    if (!currentPrice) return;

                    const sl = n(trade.stopLoss);
                    const tp = n(trade.takeProfit);

                    // Duration-based expiry
                    const expired =
                        trade.duration &&
                        trade.createdAt &&
                        now >= new Date(trade.createdAt).getTime() + trade.duration * 1000;

                    const hitSL =
                        sl > 0 &&
                        (trade.side === 'buy' ? currentPrice <= sl : currentPrice >= sl);

                    const hitTP =
                        tp > 0 &&
                        (trade.side === 'buy' ? currentPrice >= tp : currentPrice <= tp);

                    if (hitSL || hitTP || expired) {
                        const reason = hitSL ? 'stop_loss' : hitTP ? 'take_profit' : 'expired';
                        await closeTerminalTrade(trade, currentPrice, reason);
                    }
                } catch {
                    // silent
                }
            })
        );

        // ── 2. Derivative positions: stop-loss / take-profit / liquidation ─
        await Promise.all(
            forexPositions.map(async (pos) => {
                try {
                    const market = await MarketPair.findByPk(pos.market_id);
                    if (!market) return;

                    const ticker = findTicker(market.symbol);
                    if (!ticker) return;

                    // Long closes at bid, Short closes at ask
                    const exitPrice =
                        pos.side === 'long'
                            ? n(ticker.bid ?? ticker.price)
                            : n(ticker.ask ?? ticker.price);

                    if (!exitPrice) return;

                    const sl = n(pos.stop_loss);
                    const tp = n(pos.take_profit);
                    const lp = n(pos.liquidation_price);

                    const hitSL =
                        sl > 0 &&
                        (pos.side === 'long' ? exitPrice <= sl : exitPrice >= sl);

                    const hitTP =
                        tp > 0 &&
                        (pos.side === 'long' ? exitPrice >= tp : exitPrice <= tp);

                    const hitLiquidation =
                        lp > 0 &&
                        (pos.side === 'long' ? exitPrice <= lp : exitPrice >= lp);

                    if (hitLiquidation) {
                        await closeDerivativePosition(pos, exitPrice, 'liquidation');
                    } else if (hitSL) {
                        await closeDerivativePosition(pos, exitPrice, 'stop_loss');
                    } else if (hitTP) {
                        await closeDerivativePosition(pos, exitPrice, 'take_profit');
                    }
                } catch {
                    // silent
                }
            })
        );

        // ── 3. Expired binary/option trades ───────────────────────────────
        await Promise.all(
            optionTrades.map(async (trade) => {
                try {
                    await settleOptionTrade(trade);
                } catch {
                    // silent
                }
            })
        );

        // ── 4. Margin checks & cross-account liquidations ─────────────────
        await runMarginChecks();

    } catch {
        // silent
    } finally {
        running = false;
    }
}

// After (fixed — cronInterval lives at module scope)
let cronInterval = null;

function start() {
    if (cronInterval) return; // prevent double-start
    cronInterval = setInterval(runTradingCron, 5_000);
}

function stop() {
    clearInterval(cronInterval);
    cronInterval = null;
}

process.on('SIGTERM', stop);
process.on('SIGINT',  stop);

module.exports = { start, stop };
