const { Op } = require('sequelize');
const { Asset, TradingPair, Coin, SymbolStats, Market, CoinMarket, User, Wallet, AssetHolding, UserBalance, AssetLot, sequelize, MarketPair, MarketStats } = require('../models');
const { NotFoundError } = require('../utils/errors');
const { mapPrice, buildClosedHoldingChangeSeries, getUploadedFile, mapPair } = require('../utils/helpers');

const { generatePriceSeries, generatePortfolioSeries, generateMonthlyPnl } = require("../utils/portfolioSim");


const safeNumber = (v) => (isNaN(Number(v)) ? 0 : Number(v));
const formatMoney = (v) =>
  Number(safeNumber(v).toFixed(2));


// ─── Rate model alias — adjust to match your actual Sequelize association name ─
const RATE_INCLUDE = { model: SymbolStats, as: 'rate' }; // ← change 'Rate' / alias as needed
const typeMap = {
  fx: 'fx',
  crypto: 'spot',
  stock: 'stock',
  commodity: 'commodity',
  index: 'index',
}
class MarketService {

  /**
   * Get all assets
   */
  async getAssets(filters = {}) {
    const { type, status, search, page = 1, limit = 50 } = filters;

    // Default status = true unless explicitly provided
    const resolvedStatus = typeof status !== 'undefined' ? status : true;

    // WHERE for Asset
    //const assetWhere = { status: resolvedStatus };
    const assetWhere = {};
    if (type) assetWhere.type = type;

    if (search) {
      assetWhere[Op.or] = [
        { symbol: { [Op.iLike]: `%${search}%` } },
        { name: { [Op.iLike]: `%${search}%` } },
      ];
    }

    // WHERE for Coin
    const coinWhere = { status: resolvedStatus };

    if (search) {
      coinWhere[Op.or] = [
        { symbol: { [Op.iLike]: `%${search}%` } },
        { name: { [Op.iLike]: `%${search}%` } },
      ];
    }

    const [assets,
      pairs
    ] = await Promise.all([
      Asset.findAll({
        where: assetWhere,
      }),
      MarketPair.findAll({
        include: [RATE_INCLUDE, { model: Asset, as: 'base' }, { model: Asset, as: 'quote' }],
      })
    ]);


    //const symbolPrice = symbolStats.map(s => mapPrice(s))

    const mappedAssets = assets.map(a => ({
      id: a.id,
      name: a.name,
      symbol: a.symbol,
      price: a.rate?.price ?? null,
      decimal: a.decimals,
      type: a.type,
      logo: getUploadedFile(a.logo, 'assets'),
      change24h: a.rate?.change24h ?? 0,
      low24h: a.rate?.low24h ?? 0,
      high24h: a.rate?.high24h ?? 0,
    }));

    const count = mappedAssets.length;
    const start = (Number(page) - 1) * Number(limit);
    const end = start + Number(limit);

    const mappedRate = pairs.map(
      p => ({
        symbol: p.symbol,
        baseAsset: p.base?.symbol,
        quoteAsset: p.quote?.symbol,
        market: p.rate?.volume24h,
        price: p.rate?.current_price ?? null,
        change24h: p.rate?.change24h ?? 0,
        low24h: p.rate?.low24h ?? 0,
        high24h: p.rate?.high24h ?? 0,
        changePercent24h: p.rate?.change_percent24h ?? 0,
      })
    )


    return {
      assets: mappedAssets,
      prices: mappedRate,
      pagination: {
        total: count,
        page: Number(page),
        limit: Number(limit),
        totalPages: Math.ceil(count / Number(limit)),
      },
    };
  }

  async searchMarket(search) {

    // WHERE for Asset
    //const assetWhere = { status: resolvedStatus };
    const limit = 15
    let assetWhere = {}
    if (search) {
      assetWhere[Op.or] = [
        { symbol: { [Op.like]: `%${search}%` } },
        { name: { [Op.like]: `%${search}%` } },
      ];
    }



    const [assets,
      //symbolStats
    ] = await Promise.all([
      Asset.findAll({
        where: assetWhere,
        limit,
      }),
    ]);


    //const symbolPrice = symbolStats.map(s => mapPrice(s))

    const mappedAssets = assets.map(a => ({
      id: a.id,
      name: a.name,
      symbol: a.symbol,
      price: a.rate?.price ?? null,
      decimal: a.decimals,
      type: a.type,
      logo: a.logo,
      change24h: a.rate?.change24h ?? 0,
      low24h: a.rate?.low24h ?? 0,
      high24h: a.rate?.high24h ?? 0,
    }));

    const count = mappedAssets.length;


    return {
      assets: mappedAssets,
      assetCount: count
    };
  }


  /**
   * Get asset by symbol
   */
  async getAssetBySymbol(symbol) {
    const sym = String(symbol || "").toUpperCase().trim();

    const asset = await Asset.findOne({
      where: { symbol: sym },
      include: [{ model: MarketStats, as: 'stats' }],
    });

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

    const stats = await MarketStats.findOne({
      where: { pairId: asset.id }
    })
    // Similar assets (same type, exclude current)
    const count = await Asset.count({
      where: {
        type: asset.type,
        id: { [Op.ne]: asset.id },
      }
    });

    const randomOffset = Math.max(0, Math.floor(Math.random() * (count - 5)));

    const similar = await Asset.findAll({
      where: {
        type: asset.type,
        id: { [Op.ne]: asset.id },
      },
      offset: randomOffset,
      limit: 5,
    });
    // Build markets list
    const markets = [];
    let details = {}
    // If it's a stock, you may treat it as its own "pair"
    if (asset.type === "stock") {
      markets.push({
        pair: asset.symbol,
        price: 0,
        volume: 0,
        type: "stock",
      });
    }

    if (asset.type === "crypto") {
      details = await CoinMarket.findOne({
        where: { symbol: asset.symbol }
      })
    }

    // Find markets where asset is base OR quote
    const tradingMarkets = await MarketPair.findAll({
      where: {
        [Op.or]: [
          { baseAssetId: asset.id },
          { quoteAssetId: asset.id },
        ],
        type: typeMap[asset.type] || 'spot',
      },
      include: [RATE_INCLUDE],
      order: [["created_at", "DESC"]],
    });


    /* for (const m of tradingMarkets) {
      markets.push({
        pair: m.symbol,     // or m.pair or whatever your column is
        price: 0,
        volume: 0,
        type: m.type,       // e.g. spot/forex/crypto etc
      });
    }
 */

    const seen = new Set();

    const tradings =  tradingMarkets.reduce((acc, a) => {
      const m = mapPair(a);
      m.pair = m.symbol;

      if (!seen.has(m.symbol)) {
        seen.add(m.symbol);
        acc.push(m);
      }

      return acc;
    }, [])

    return {
      apiAsset: {
        id: asset.id,
        name: asset.name,
        symbol: asset.symbol,
        type: asset.type,
        description: asset.description,
        logo: getUploadedFile(asset.logo, 'assets')
      },
      similarAssets: similar.map(s => ({
        name: s.name,
        symbol: s.symbol,
        logo: getUploadedFile(s.logo, 'assets'),
        price: 0,
        change: 0,
      })),
      tradingMarkets: tradings,
      marketStat: stats ? stats.toJSON() : {},
    };
  }

  /**
   * Get all trading pairs
   */
  async getTradingPairs(filters = {}) {
    const { market, quoteAsset, status, search, page = 1, limit = 50 } = filters;

    const where = {};
    if (market) where.market = market;
    if (quoteAsset) where.quoteAsset = quoteAsset.toUpperCase();
    if (status) where.status = status;
    if (search) {
      where.symbol = { [Op.iLike]: `%${search}%` };
    }

    const { count, rows } = await TradingPair.findAndCountAll({
      where,
      order: [['isFeatured', 'DESC'], ['volume24h', 'DESC']],
      limit,
      offset: (page - 1) * limit,
    });

    return {
      pairs: rows,
      pagination: { total: count, page, limit, totalPages: Math.ceil(count / limit) },
    };
  }

  async getPortfolio(userId) {
    const [user, assets, lots, balance] = await Promise.all([
      User.findOne({
        where: { id: userId },
        include: [{ model: UserBalance, as: "balance" }],
      }),

      AssetHolding.findAndCountAll({
        where: { userId },
        include: [{ model: MarketPair, as: 'asset', include: [{ model: Asset, as: 'base' }, {model: SymbolStats, as: 'rate'}] }],
      }),
      AssetLot.findAll({
        where: { userId }
      }),
      UserBalance.findOne({ where: { userId } }),
    ]);


    const tasset = (assets?.rows ?? []).map((a) => ({
      id: a.id,
      symbol: a.asset?.symbol,
      name: a.asset?.name,
      price: a.asset?.rate?.current_price, // filled from SymbolStats
      amount: 0,
      change: null,
      volume: null,
      marketCap: null,
      holdings: safeNumber(a.quantity),
      dividends: safeNumber(a.dividends),
      category: "asset",
      logo: getUploadedFile(a.asset?.base?.logo ?? a.logo, 'assets'),
      source: "asset",
      avgCost: a.avgBuyPrice,
    }));

    const both = [...tasset].filter((x) => x.symbol);

    // ✅ pull prices from SymbolStats
    const symbols = [...new Set(both.map((b) => b.id).filter(Boolean))];
    const statsRows = await SymbolStats.findAll({
      where: { asset_id: { [Op.in]: symbols } },
    });

    const statsBySymbol = Object.fromEntries(
      statsRows.map((r) => {
        const j = r.toJSON ? r.toJSON() : r;
        return [j.symbol, j];
      })
    );



    // ✅ attach rate + compute amounts
    for (const item of both) {
      const s = statsBySymbol[item.symbol];
      const p = safeNumber(s?.price) || item.price;

      item.price = p;
      item.change = s?.change24h ?? null;
      item.volume = s?.volume ?? null;
      item.marketCap = s?.marketCap ?? null;
      item.amount = p * safeNumber(item.holdings);

      // if you have avgCost in DB, plug it here; otherwise simulate (stable-ish)
      const pseudo = (String(userId).length + item.symbol.length) % 7;
      const costFactor = 0.82 + pseudo * 0.015; // 0.82..0.91
    }

    // ✅ fixed bug: your previous topAssets logic was wrong
    const topAssets = both
      .filter((b) => safeNumber(b.amount) > 0)
      .sort((a, b) => safeNumber(b.amount) - safeNumber(a.amount))
      .slice(0, 6);

    const totalPortfolio = both.reduce((sum, i) => sum + safeNumber(i.amount), 0);

    const fundingBalance = safeNumber(balance?.balance);
    const totalValue = fundingBalance + totalPortfolio;
    const totalCost = assets?.rows?.reduce((acc, a) => {
      const t = a.quantity * a.avgBuyPrice
      return acc + t;
    }, 0)
    const stats = {
      totalBalance: totalValue,
      totalPortfolio,
      totalCost,
      totalDeposits: safeNumber(0),
      fundingBalance,
      assetCount: safeNumber(assets?.count),
      totalValue, // ✅ frontend expects this name often
    };

    const distribution = (() => {
      const sorted = [...both].sort((a, b) => safeNumber(b.amount) - safeNumber(a.amount));
      const total = sorted.reduce((sum, x) => sum + safeNumber(x.amount), 0);

      if (total <= 0) return [];

      const top6 = sorted.slice(0, 6);
      const rest = sorted.slice(6);

      const otherCryptoAmount = rest
        .filter((x) => x.source === "crypto")
        .reduce((sum, x) => sum + safeNumber(x.amount), 0);

      const otherAssetsAmount = rest
        .filter((x) => x.source === "asset")
        .reduce((sum, x) => sum + safeNumber(x.amount), 0);

      const topRows = top6.map((x) => ({
        name: x.name || x.symbol || "Unknown",
        value: Number(((safeNumber(x.amount) / total) * 100).toFixed(2)),
        amount: formatMoney(x.amount),
        symbol: x.symbol,
        id: x.id,
      }));

      const buckets = [

        {
          name: "Other Assets",
          value: Number(((otherAssetsAmount / total) * 100).toFixed(2)),
          amount: formatMoney(otherAssetsAmount),
        },
      ].filter((b) => b.value > 0);

      return [...topRows, ...buckets];
    })();

    // ✅ simulate per-asset price series + portfolio growth (1D/1W/1M/3M/1Y/ALL)
    const periods = { "1D": 24, "1W": 7, "1M": 30, "3M": 90, "1Y": 365, ALL: 730 };

    const priceGrowth = {};
    const portfolioGrowth = {};

    for (const [k, points] of Object.entries(periods)) {
      const seriesBySymbol = {};
      for (const it of both) {
        seriesBySymbol[it.symbol] = generatePriceSeries({
          userId,
          symbol: it.symbol,
          currentPrice: it.price,
          points,
        });
      }

      priceGrowth[k] = seriesBySymbol;
      portfolioGrowth[k] = generatePortfolioSeries({
        items: both,
        seriesBySymbol,
        points,
      });
    }

    const monthlyPnl = generateMonthlyPnl({
      userId,
      portfolioSeries: portfolioGrowth["1Y"],
    });

    // ✅ holdings enriched for table (pnl)
    const holdings = both
      .filter((x) => safeNumber(x.holdings) > 0)
      .map((x) => {
        const value = safeNumber(x.amount);
        const cost = safeNumber(x.avgCost) * safeNumber(x.holdings);
        const pnl = value - cost;
        const pnlPercent = cost > 0 ? (pnl / cost) * 100 : 0;
        return {
          symbol: x.symbol,
          name: x.name,
          amount: safeNumber(x.holdings),
          avgCost: safeNumber(x.avgCost),
          currentPrice: safeNumber(x.price),
          value: Number(value.toFixed(2)),
          cost: Number(cost.toFixed(2)),
          pnl: Number(pnl.toFixed(2)),
          dividends: safeNumber(x.dividends),
          pnlPercent: Number(pnlPercent.toFixed(2)),
          allocation: totalPortfolio > 0 ? Number(((value / totalPortfolio) * 100).toFixed(1)) : 0,
          color: null, // frontend can derive, or you can store in Coin/Asset metadata
          category: x.source === "crypto" ? "spot" : "spot",
          logo: x.asset?.logo ?? x.logo,
        };
      })
      .sort((a, b) => safeNumber(b.value) - safeNumber(a.value));



    return {
      porfolios: both, // (kept your spelling)
      lots,
      user: user.toJSON,
      stats,
      distribution,
      chartData: buildClosedHoldingChangeSeries(lots),
      topAssets,
      holdings,              // ✅ table uses this
      priceGrowth,           // ✅ per-asset simulated series per period
      portfolioGrowth,       // ✅ portfolio series per period
      monthlyPnl,            // ✅ bar chart uses this
    };
  }

  /**
   * Get trading pair by symbol
   */
  async getTradingPairBySymbol(symbol, market = null) {
    const where = { symbol: symbol.toUpperCase() };
    if (market) where.market = market;

    const pair = await TradingPair.findOne({ where });

    if (!pair) {
      throw new NotFoundError('Trading pair not found');
    }

    return pair;
  }

  /**
   * Get market overview
   */
  async getMarketOverview() {
    const [totalAssets, totalPairs, activePairs] = await Promise.all([
      Asset.count({ where: { status: 'active' } }),
      TradingPair.count(),
      TradingPair.count({ where: { status: 'active', tradingEnabled: true } }),
    ]);

    const topGainers = await TradingPair.findAll({
      where: { status: 'active' },
      order: [['priceChangePercent24h', 'DESC']],
      limit: 5,
    });

    const topLosers = await TradingPair.findAll({
      where: { status: 'active' },
      order: [['priceChangePercent24h', 'ASC']],
      limit: 5,
    });

    const topVolume = await TradingPair.findAll({
      where: { status: 'active' },
      order: [['volume24h', 'DESC']],
      limit: 5,
    });

    return {
      totalAssets,
      totalPairs,
      activePairs,
      topGainers,
      topLosers,
      topVolume,
    };
  }

  /**
   * Get ticker data
   */
  async getTicker(symbol) {
    const pair = await this.getTradingPairBySymbol(symbol);

    return {
      symbol: pair.symbol,
      price: pair.price,
      bidPrice: pair.bidPrice,
      askPrice: pair.askPrice,
      priceChange24h: pair.priceChange24h,
      priceChangePercent24h: pair.priceChangePercent24h,
      high24h: pair.high24h,
      low24h: pair.low24h,
      volume24h: pair.volume24h,
      quoteVolume24h: pair.quoteVolume24h,
      updatedAt: pair.priceUpdatedAt,
    };
  }

  /**
   * Get all tickers
   */
  async getAllTickers(market = null) {
    const where = { status: 'active' };
    if (market) where.market = market;

    const pairs = await TradingPair.findAll({
      where,
      attributes: [
        'symbol', 'price', 'bidPrice', 'askPrice',
        'priceChange24h', 'priceChangePercent24h',
        'high24h', 'low24h', 'volume24h', 'quoteVolume24h',
      ],
    });

    return pairs;
  }

  /**
   * Get orderbook (mock data)
   */
  async getOrderbook(symbol, limit = 20) {
    const pair = await this.getTradingPairBySymbol(symbol);
    const price = parseFloat(pair.price);

    // Generate mock orderbook
    const bids = [];
    const asks = [];

    for (let i = 0; i < limit; i++) {
      const bidPrice = price * (1 - (i + 1) * 0.001);
      const askPrice = price * (1 + (i + 1) * 0.001);
      const quantity = Math.random() * 10;

      bids.push({ price: bidPrice.toFixed(8), quantity: quantity.toFixed(8) });
      asks.push({ price: askPrice.toFixed(8), quantity: quantity.toFixed(8) });
    }

    return { symbol, bids, asks, timestamp: Date.now() };
  }

  /**
   * Get recent trades (mock data)
   */
  async getRecentTrades(symbol, limit = 50) {
    const pair = await this.getTradingPairBySymbol(symbol);
    const price = parseFloat(pair.price);

    const trades = [];
    const now = Date.now();

    for (let i = 0; i < limit; i++) {
      trades.push({
        id: `${now}-${i}`,
        price: (price * (1 + (Math.random() - 0.5) * 0.01)).toFixed(8),
        quantity: (Math.random() * 5).toFixed(8),
        side: Math.random() > 0.5 ? 'buy' : 'sell',
        timestamp: now - i * 1000,
      });
    }

    return trades;
  }

  /**
   * Get candlestick data (mock data)
   */
  async getKlines(symbol, interval = '1h', limit = 100) {
    const pair = await this.getTradingPairBySymbol(symbol);
    const price = parseFloat(pair.price);

    const intervals = {
      '1m': 60 * 1000,
      '5m': 5 * 60 * 1000,
      '15m': 15 * 60 * 1000,
      '1h': 60 * 60 * 1000,
      '4h': 4 * 60 * 60 * 1000,
      '1d': 24 * 60 * 60 * 1000,
    };

    const intervalMs = intervals[interval] || intervals['1h'];
    const now = Date.now();
    const klines = [];

    let currentPrice = price * 0.9;

    for (let i = limit - 1; i >= 0; i--) {
      const open = currentPrice;
      const change = (Math.random() - 0.48) * 0.02;
      const close = open * (1 + change);
      const high = Math.max(open, close) * (1 + Math.random() * 0.01);
      const low = Math.min(open, close) * (1 - Math.random() * 0.01);
      const volume = Math.random() * 1000;

      klines.push({
        openTime: now - i * intervalMs,
        open: open.toFixed(8),
        high: high.toFixed(8),
        low: low.toFixed(8),
        close: close.toFixed(8),
        volume: volume.toFixed(8),
        closeTime: now - i * intervalMs + intervalMs - 1,
      });

      currentPrice = close;
    }

    return klines;
  }



  async getAssetLots(userId) {
    const holdings = await AssetLot.findAndCountAll({
      where: { userId },
      include: [{ model: MarketPair, as: 'pair', include: [{ model: Asset, as: 'base' }] }],
      order: [["createdAt", "DESC"]],
    });

    const stats = await AssetLot.findOne({
      where: { userId },
      attributes: [
        [sequelize.fn("COUNT", sequelize.col("id")), "totalLots"],

        [
          sequelize.fn(
            "SUM",
            sequelize.literal(`CASE WHEN side = 'buy' THEN 1 ELSE 0 END`)
          ),
          "buyCount",
        ],

        [
          sequelize.fn(
            "SUM",
            sequelize.literal(`CASE WHEN side = 'sell' THEN 1 ELSE 0 END`)
          ),
          "sellCount",
        ],

        [
          sequelize.fn(
            "SUM",
            sequelize.literal(
              `CASE WHEN side = 'buy' THEN quantity * buy_price ELSE 0 END`
            )
          ),
          "totalBought",
        ],

        [
          sequelize.fn(
            "SUM",
            sequelize.literal(
              `CASE WHEN side = 'sell' THEN quantity * buy_price ELSE 0 END`
            )
          ),
          "soldCost",
        ],
      ],
      raw: true,
    });

    return {
      lots: holdings.rows.map(l => ({
        time: l.createdAt,
        symbol: l.pair?.symbol ?? '',
        name: l.pair?.name ?? '',
        logo: getUploadedFile(l.pair?.base?.logo ?? null, 'assets'),
        side: l.side,
        type: l.type,
        quantity: l.quantity,
        buyPrice: l.buyPrice ?? 0,
        marketLimit: l.limit,
        status: l.status,

      })),
      count: holdings.count,

      stats: {
        totalLots: Number(stats?.totalLots || 0),
        buyCount: Number(stats?.buyCount || 0),
        sellCount: Number(stats?.sellCount || 0),
        totalBought: Number(stats?.totalBought || 0),
        soldCost: Number(stats?.soldCost || 0),
      },
    };
  }

  async getPortfolioChart(userId) {
    const lots = await AssetLot.findAll({
      where: { userId }
    })

    return { chartData: buildClosedHoldingChangeSeries(lots) }
  }

}

module.exports = new MarketService();
