const { Op } = require('sequelize');
const { Notification, MarketPair, UserBalance, BinaryTrade, sequelize, WalletLedger, SymbolStats, Asset } = require('../models');
const { NotFoundError, BadRequestError } = require('../utils/errors');
const { getUploadedFile } = require('../utils/helpers');
const forexMarketData = require('./forexMarketData');
const stockMarketData = require('./stockMarketData');
const cryptoMarketData = require('./cryptoMarketData');
const n = (value) => Number(value || 0);

const round4 = (value) => Number(n(value).toFixed(4));
const round6 = (value) => Number(n(value).toFixed(6));
class OptionsService {
  async getIndex(userId) {
    const [
      pairs,
      balance,
      openDeals,
      closedDeals
    ] = await Promise.all([
      MarketPair.findAll({
        where: {
          type: {
            [Op.in]: ['spot', 'fx', 'stock']
          }
        },
        include: [{ model: Asset, as: 'base' }, { model: Asset, as: 'quote' }, { model: SymbolStats, as: 'rate' }]
      }),
      UserBalance.findOne({
        where: { userId }
      }),
      BinaryTrade.findAll({
        where: { userId, status: 'open' },
        include: [{ model: MarketPair, as: 'pair' }],
        order: [['createdAt', 'desc']]
      }),
      BinaryTrade.findAll({
        where: {
          userId,
          status: {
            [Op.ne]: 'open'
          }
        },
        include: [{ model: MarketPair, as: 'pair' }],
        order: [['createdAt', 'desc']]
      })
    ])

    return {
      pairs: pairs.map(p => {
        const baseAsset = p?.base ?? '';
        const quoteAsset = p?.quote ?? '';
        if (!baseAsset && !quoteAsset) return
        return {
          pair: p.name,
          symbol: p.symbol,
          base: baseAsset.symbol,
          quote: quoteAsset.symbol,
          logo: getUploadedFile(baseAsset.logo, 'assets'),
          type: p.type,
          baseLogo: getUploadedFile(baseAsset.logo, 'assets'),
          quoteLogo: getUploadedFile(quoteAsset.logo, 'assets'),
          price: Number(p.rate?.current_price ?? 0),
          low: Number(p.rate?.low24h ?? 0),
          high: Number(p.rate?.high24h ?? 0),
          volume: Number(p.rate?.volume24h ?? 0),
          change24h: Number(p.rate?.change_percent24h ?? 0),

        }
      }),
      //pairs,
      balance: balance.balance,
      openPositions: openDeals.map(d => {
        const dateObj = new Date(d.createdAt);

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

        const date = dateObj.toISOString().split("T")[0];
        const time = dateObj.toISOString().split("T")[1].slice(0, 8);
        return {
          id: d.id,
          date: date,
          asset: d?.pair.name,
          symbol: d?.pair.symbol,
          profit: d.potentialReturn,
          duration: d.durationSeconds,
          direction: d.direction,
          time: time,
          amount: d.investment,
          type: d.status,
          expires: d.expiresAt,
          createdAt: d.createdAt,
        }
      })
    }
  }

  computeTradeMath(investment, payoutPercent) {
    const inv = n(investment);
    const payout = n(payoutPercent) / 100;
    const potentialProfit = round4(inv * payout);
    const potentialReturn = round4(inv + potentialProfit);

    return { potentialProfit, potentialReturn };
  }

  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 getLatestPriceOrThrow(assetId) {
    const latest = await MarketPrice.findOne({
      where: { assetId },
      order: [["capturedAt", "DESC"]],
      include: [{ model: SymbolStats, as: 'rate' }]
    });

    if (!latest) {
      throw new Error("No market price available for this asset");
    }

    return latest;
  }

  async mutateWalletWithLedger({
    transaction,
    userId,
    walletType,
    tradeId = null,
    delta,
    ledgerType,
    note,
  }) {
    const wallet = await UserBalance.findOne({
      where: { userId },
      transaction,
      lock: transaction.LOCK.UPDATE,
    });

    if (!wallet) throw new Error("Wallet not found");

    const before = walletType === "real" ? n(wallet.balance) : n(wallet.demoBalance);
    const after = round4(before + n(delta));

    if (after < 0) throw new Error("Insufficient balance");

    if (walletType === "real") {
      wallet.balance = after;
    } else {
      wallet.demoBalance = after;
    }

    await wallet.save({ transaction });

    await WalletLedger.create(
      {
        userId,
        walletType,
        tradeId,
        type: ledgerType,
        amount: round4(delta),
        balanceBefore: before,
        balanceAfter: after,
        note,
      },
      { transaction }
    );

    return wallet;
  }


  async placeTrade(userId, {
    symbol,
    direction,
    investment,
    durationSeconds,
    walletType = "demo",
    price
  }) {
    const transaction = await sequelize.transaction();

    try {
      if (!["buy", "sell"].includes(direction)) {
        throw new Error("direction must be buy or sell");
      }


      if (durationSeconds < 60 || durationSeconds > 900) {
        throw new Error("Unsupported expiry duration");
      }

      const amount = round4(investment);
      if (amount <= 0) {
        throw new Error("Investment must be greater than 0");
      }

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

      if (!asset) throw new Error("Asset not found or inactive");

      const balance = await UserBalance.findOne({
        where: { userId },
        transaction,
      });

      if (!balance) throw new Error("Balance not found or inactive");

      if (balance < price) throw new Error("Insufficient Balance");

      const entryPrice = round6(price);
      const payoutPercent = round4(asset.payoutPercent ?? 63);
      const { potentialProfit, potentialReturn } = this.computeTradeMath(amount, payoutPercent);

      await this.mutateWalletWithLedger({
        transaction,
        userId,
        walletType,
        delta: -amount,
        ledgerType: "trade_debit",
        note: `Binary trade opened on ${symbol}`,
      });

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

      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: "open",
          meta: {
            assetName: asset.name,
            sourcePriceId: price,
          },
        },
        { transaction }
      );

      await transaction.commit();
      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);

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

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

  async settleTrade(userId, data) {
    const { tradeId, price = null } = data;
    const transaction = await sequelize.transaction();

    try {
      const trade = await BinaryTrade.findByPk(tradeId, {
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!trade) throw new Error("Trade not found");



      if (trade.status !== "open") {
        await transaction.commit();
        return trade;
      }

      const pair = await MarketPair.findOne({
        where: {
          id: trade.assetId
        }
      })

      if (!pair) throw new Error("Trading Pair not found");

      const balance = await UserBalance.findOne({
        where: { userId },
        transaction,
      });

      if (!balance) throw new Error("Balance not found or inactive");

      const px = round6(price)
      let resultPrice;

      if (price < 0) {
        const latest = this.findTicker(pair?.symbol);
        resultPrice = round6(latest.price);
      } else {
        resultPrice = px
      }


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

      let payoutAmount = 0;
      let profit = 0;

      if (status === "won") {
        const bal = Number(balance.balance)
        payoutAmount = round4(trade.potentialReturn);
        profit = round4(trade.potentialProfit);

      }

      trade.resultPrice = resultPrice;
      trade.status = status;
      trade.profit = profit;
      trade.payoutAmount = payoutAmount;
      trade.settledAt = new Date();

      await trade.save({ transaction });

      await transaction.commit();
      return trade;
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }

  async settleUserTrade(userId, data) {
    const { tradeId, price = null } = data;
    const transaction = await sequelize.transaction();

    try {
      const trade = await BinaryTrade.findByPk(tradeId, {
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!trade) throw new Error("Trade not found");

      if (trade.status !== "open") {
        await transaction.rollback();
        return trade;
      }

      const pair = await MarketPair.findOne({
        where: {
          id: trade.assetId
        }
      })

      if (!pair) throw new Error("Trading Pair not found");

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

      if (!balance) throw new Error("Balance not found");

      let resultPrice;

      if (price < 0 || price === null) {
        const latest = this.findTicker(pair?.symbol);
        resultPrice = round6(latest.price);
      } else {
        resultPrice = px
      }

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

      let payoutAmount = 0;
      let profit = 0;

      if (status === "won") {
        payoutAmount = round4(trade.potentialReturn);
        profit = payoutAmount - trade.investment;

        await balance.update({
          balance: Number(balance.balance || 0) + payoutAmount
        }, { transaction });

      } else if (status === "draw") {
        payoutAmount = round4(trade.investment);

        await balance.update({
          balance: Number(balance.balance || 0) + payoutAmount
        }, { transaction });
      }

      trade.resultPrice = resultPrice;
      trade.status = status;
      trade.profit = profit;
      trade.payoutAmount = payoutAmount;
      trade.settledAt = new Date();

      await trade.save({ transaction });

      await transaction.commit();

      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);

          const date = dateObj.toISOString().split("T")[0];
          const time = dateObj.toISOString().split("T")[1].slice(0, 8);
          return {
            id: d.id,
            date: date,
            asset: d?.pair.name,
            symbol: d?.pair.symbol,
            profit: d.potentialReturn,
            duration: d.durationSeconds,
            direction: d.direction,
            time: time,
            entry: d.entryPrice,
            amount: d.investment,
            type: d.status,
            expires: d.expiresAt,
            createdAt: d.createdAt,
          }
        }),

      };

    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }


  async settleExpiredTrades(limit = 100) {
    const trades = await BinaryTrade.findAll({
      where: {
        status: "open",
        expiresAt: { [Op.lte]: new Date() },
      },
      order: [["expiresAt", "ASC"]],
      limit,
    });

    const results = [];

    for (const trade of trades) {
      try {
        const settled = await settleTrade(trade.id);
        results.push({ id: trade.id, status: settled.status });
      } catch (error) {
        results.push({ id: trade.id, error: error.message });
      }
    }

    return results;
  }

  async getUserTrades(userId) {
    const trades = await BinaryTrade.findAll({
      where: { userId },
      order: [["createdAt", "DESC"]],
      limit: 50,
    });

    const summary = {
      open: 0,
      won: 0,
      lost: 0,
      draw: 0,
      totalInvestment: 0,
      totalProfit: 0,
    };

    for (const trade of trades) {
      summary[trade.status] = (summary[trade.status] || 0) + 1;
      summary.totalInvestment += n(trade.investment);
      summary.totalProfit += n(trade.profit);
    }

    summary.totalInvestment = round4(summary.totalInvestment);
    summary.totalProfit = round4(summary.totalProfit);

    return { summary, trades };
  }



}

module.exports = new OptionsService();