/**
 * Galaxy Strike Online — Player Market (GDD §14)
 * Types: Auction, Direct Sale
 * 2% listing fee; 24/48/72-hour durations
 * Tradeable: resources, modules, blueprints, consumables, cosmetics
 * v3.2: Price history tracking — rolling 30-day window, per-item median/sparkline data
 */

const mongoose = require('mongoose');

const listingSchema = new mongoose.Schema({
  listingId:     { type: String, required: true, unique: true },
  sellerId:      { type: String, required: true },
  sellerName:    { type: String, default: '' },
  itemType:      { type: String, required: true }, // 'resource'|'item'
  itemId:        { type: String, required: true },
  itemName:      { type: String, default: '' },
  itemIcon:      { type: String, default: '📦' },
  quantity:      { type: Number, default: 1 },
  pricePerUnit:  { type: Number, required: true },
  totalPrice:    { type: Number, required: true },
  currency:      { type: String, default: 'credits' },
  listedAt:      { type: Date,   default: Date.now },
  expiresAt:     { type: Date,   required: true },
  isActive:      { type: Boolean, default: true },
  buyerId:       { type: String, default: null },
  soldAt:        { type: Date,   default: null },
  durationHours: { type: Number, default: 24 },
}, { timestamps: true });

listingSchema.index({ isActive: 1, expiresAt: 1 });
listingSchema.index({ sellerId: 1 });
listingSchema.index({ itemId: 1, isActive: 1 });
listingSchema.index({ itemId: 1, soldAt: -1 });  // for price history queries

const Listing = mongoose.model('MarketListing', listingSchema);

// ── Price History Schema (GDD §14 v3.2 extension) ─────────────────────────
// Stores daily OHLC-style buckets per item for sparkline/median display
const priceHistorySchema = new mongoose.Schema({
  itemId:     { type: String, required: true },
  date:       { type: String, required: true }, // 'YYYY-MM-DD'
  open:       { type: Number, default: 0 },
  high:       { type: Number, default: 0 },
  low:        { type: Number, default: 0 },
  close:      { type: Number, default: 0 },
  volume:     { type: Number, default: 0 }, // units sold
  saleCount:  { type: Number, default: 0 }, // number of transactions
  sumPrice:   { type: Number, default: 0 }, // for average calculation
}, { timestamps: true });
priceHistorySchema.index({ itemId: 1, date: -1 }, { unique: true });

const PriceHistory = mongoose.model('MarketPriceHistory', priceHistorySchema);

const LISTING_FEE_PCT = 0.02;
const MAX_LISTINGS    = 10;
const VALID_DURATIONS = [24, 48, 72];
const HISTORY_DAYS    = 30; // rolling window kept

const TRADEABLE_RESOURCES = {
  metal:       { name:'Metal',        icon:'⚙', minPrice:1,   maxPrice:100  },
  gas:         { name:'Gas',          icon:'☁', minPrice:1,   maxPrice:100  },
  crystal:     { name:'Crystal',      icon:'💎', minPrice:2,  maxPrice:200  },
  energyCells: { name:'Energy Cells', icon:'⚡', minPrice:1,  maxPrice:80   },
  darkMatter:  { name:'Dark Matter',  icon:'✦', minPrice:10,  maxPrice:2000 },
};

class MarketSystem {
  constructor() {
    this.Listing      = Listing;
    this.PriceHistory = PriceHistory;
  }

  // ── Internal: record a sale into price history buckets ──────────────────
  async _recordSale(itemId, pricePerUnit, quantity) {
    try {
      const date = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
      const existing = await PriceHistory.findOne({ itemId, date });
      if (existing) {
        existing.high      = Math.max(existing.high, pricePerUnit);
        existing.low       = existing.low > 0 ? Math.min(existing.low, pricePerUnit) : pricePerUnit;
        existing.close     = pricePerUnit;
        existing.volume   += quantity;
        existing.saleCount += 1;
        existing.sumPrice  += pricePerUnit * quantity;
        await existing.save();
      } else {
        await PriceHistory.create({
          itemId, date,
          open: pricePerUnit, high: pricePerUnit, low: pricePerUnit, close: pricePerUnit,
          volume: quantity, saleCount: 1, sumPrice: pricePerUnit * quantity,
        });
      }
      // Prune old records beyond HISTORY_DAYS
      const cutoff = new Date();
      cutoff.setDate(cutoff.getDate() - HISTORY_DAYS);
      const cutoffStr = cutoff.toISOString().slice(0, 10);
      await PriceHistory.deleteMany({ itemId, date: { $lt: cutoffStr } });
    } catch (e) {
      console.warn('[MarketSystem] _recordSale error (non-fatal):', e.message);
    }
  }

  // ── Public: get price history for an item (last N days) ─────────────────
  async getPriceHistory(itemId, days = 14) {
    const cutoff = new Date();
    cutoff.setDate(cutoff.getDate() - days);
    const cutoffStr = cutoff.toISOString().slice(0, 10);
    const rows = await PriceHistory.find({ itemId, date: { $gte: cutoffStr } })
      .sort({ date: 1 }).lean();

    const median = rows.length > 0
      ? rows.reduce((s, r) => s + (r.sumPrice / Math.max(r.volume, 1)), 0) / rows.length
      : null;

    return {
      itemId,
      days: rows.map(r => ({
        date:      r.date,
        open:      r.open,
        high:      r.high,
        low:       r.low,
        close:     r.close,
        volume:    r.volume,
        saleCount: r.saleCount,
        avg:       r.volume > 0 ? Math.round(r.sumPrice / r.volume) : r.close,
      })),
      median:    median !== null ? Math.round(median) : null,
      allTimeMin: rows.length ? Math.min(...rows.map(r => r.low))  : null,
      allTimeMax: rows.length ? Math.max(...rows.map(r => r.high)) : null,
    };
  }

  /** List a resource on the market */
  async listResource(playerData, { resource, quantity, pricePerUnit, durationHours = 24 }) {
    if (!TRADEABLE_RESOURCES[resource]) throw new Error('Resource not tradeable');
    quantity      = Math.floor(quantity);
    pricePerUnit  = Math.floor(pricePerUnit);
    durationHours = VALID_DURATIONS.includes(durationHours) ? durationHours : 24;

    if (quantity < 1)     throw new Error('Quantity must be ≥ 1');
    if (pricePerUnit < 1) throw new Error('Price must be ≥ 1 credit');

    const res = playerData.resources || {};
    if ((res[resource] || 0) < quantity)
      throw new Error(`Insufficient ${resource}: have ${res[resource]||0}, need ${quantity}`);

    const myListings = await Listing.countDocuments({ sellerId: playerData.userId, isActive: true });
    if (myListings >= MAX_LISTINGS) throw new Error(`Max ${MAX_LISTINGS} active listings`);

    res[resource] -= quantity;
    const totalPrice = pricePerUnit * quantity;
    const listingFee = Math.ceil(totalPrice * LISTING_FEE_PCT);
    if ((playerData.stats?.credits || 0) < listingFee) throw new Error(`Listing fee: ${listingFee} credits`);
    playerData.stats.credits -= listingFee;

    const cfg = TRADEABLE_RESOURCES[resource];
    const listing = new Listing({
      listingId:    'listing_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6),
      sellerId:     playerData.userId,
      sellerName:   playerData.username,
      itemType:     'resource',
      itemId:       resource,
      itemName:     cfg.name,
      itemIcon:     cfg.icon,
      quantity, pricePerUnit, totalPrice,
      currency:     'credits',
      expiresAt:    new Date(Date.now() + durationHours * 3600 * 1000),
      durationHours,
    });
    await listing.save();
    return { listing, listingFee };
  }

  /** List an inventory item */
  async listItem(playerData, { itemId, pricePerUnit, durationHours = 24 }) {
    const inventory = playerData.inventory?.items || playerData.inventory || [];
    const idx = inventory.findIndex(i => i && i.id === itemId);
    if (idx < 0) throw new Error('Item not found in inventory');
    const item = inventory[idx];
    if (item.type === 'ship' && item.id === playerData.stats?.activeShipId)
      throw new Error('Cannot list your active ship');

    pricePerUnit  = Math.floor(pricePerUnit);
    durationHours = VALID_DURATIONS.includes(durationHours) ? durationHours : 24;

    const myListings = await Listing.countDocuments({ sellerId: playerData.userId, isActive: true });
    if (myListings >= MAX_LISTINGS) throw new Error(`Max ${MAX_LISTINGS} active listings`);

    const listingFee = Math.ceil(pricePerUnit * LISTING_FEE_PCT);
    if ((playerData.stats?.credits || 0) < listingFee) throw new Error(`Listing fee: ${listingFee} credits`);
    playerData.stats.credits -= listingFee;
    inventory.splice(idx, 1);

    const listing = new Listing({
      listingId:  'listing_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6),
      sellerId:   playerData.userId,
      sellerName: playerData.username,
      itemType:   'item',
      itemId:     item.id,
      itemName:   item.name || item.id,
      itemIcon:   item.icon || '📦',
      quantity:   1,
      pricePerUnit,
      totalPrice: pricePerUnit,
      currency:   'credits',
      expiresAt:  new Date(Date.now() + durationHours * 3600 * 1000),
      durationHours,
      _itemData:  JSON.stringify(item),
    });
    await listing.save();
    return { listing, listingFee };
  }

  /** Buy a listing — records price history on sale */
  async buyListing(buyerData, listingId) {
    const listing = await Listing.findOne({ listingId, isActive: true });
    if (!listing)                                  throw new Error('Listing not found or already sold');
    if (listing.expiresAt < new Date())            throw new Error('Listing expired');
    if (listing.sellerId === buyerData.userId)     throw new Error('Cannot buy your own listing');
    if ((buyerData.stats?.credits || 0) < listing.totalPrice)
      throw new Error(`Need ${listing.totalPrice} credits, have ${buyerData.stats?.credits||0}`);

    buyerData.stats.credits -= listing.totalPrice;

    if (listing.itemType === 'resource') {
      if (!buyerData.resources) buyerData.resources = {};
      buyerData.resources[listing.itemId] = (buyerData.resources[listing.itemId] || 0) + listing.quantity;
    } else {
      const inv = buyerData.inventory?.items || buyerData.inventory;
      if (Array.isArray(inv)) {
        try { inv.push(JSON.parse(listing._itemData)); } catch(e) {}
      }
    }

    listing.isActive = false;
    listing.buyerId  = buyerData.userId;
    listing.soldAt   = new Date();
    await listing.save();

    // Record the sale in price history (non-blocking)
    this._recordSale(listing.itemId, listing.pricePerUnit, listing.quantity);

    return { listing, proceeds: listing.totalPrice };
  }

  /** Cancel own listing */
  async cancelListing(playerData, listingId) {
    const listing = await Listing.findOne({ listingId, sellerId: playerData.userId, isActive: true });
    if (!listing) throw new Error('Listing not found');
    listing.isActive = false;
    await listing.save();

    if (listing.itemType === 'resource') {
      if (!playerData.resources) playerData.resources = {};
      playerData.resources[listing.itemId] = (playerData.resources[listing.itemId] || 0) + listing.quantity;
    } else {
      const inv = playerData.inventory?.items || playerData.inventory;
      if (Array.isArray(inv)) {
        try { inv.push(JSON.parse(listing._itemData)); } catch(e) {}
      }
    }
    return listing;
  }

  async getListings({ itemId, category, limit = 40 } = {}) {
    const filter = { isActive: true, expiresAt: { $gt: new Date() } };
    if (itemId)   filter.itemId   = itemId;
    if (category) filter.itemType = category;
    return Listing.find(filter).sort({ pricePerUnit: 1 }).limit(limit).lean();
  }

  async getMyListings(userId) {
    return Listing.find({ sellerId: userId, isActive: true }).sort({ listedAt: -1 }).lean();
  }

  async expireListings() {
    const now = new Date();
    const expired = await Listing.find({ isActive: true, expiresAt: { $lt: now } });
    await Listing.updateMany(
      { isActive: true, expiresAt: { $lt: now } },
      { $set: { isActive: false } }
    );
    return expired.length;
  }

  getTradeableResources() { return TRADEABLE_RESOURCES; }
}

module.exports = { MarketSystem, Listing, PriceHistory };
