const db = require("../models");
const { Op } = require("sequelize");

const sequelize = db.sequelize;
const Asset = db.Asset;
const Wallet = db.Wallet;
const OptionChain = db.OptionChain;
const OptionOrder = db.OptionOrder;
const OptionPosition = db.OptionPosition;
const MarketPair = db.MarketPair

class OptionTradingService {
  async getOptionsIndex({ userId }) {
    const assets = await MarketPair.findAll({
      where: {
        type: {
          [Op.in]: ["stock", "index", "etf", "spot"],
        },
        optionsEnabled: true,
      },
    
      order: [["symbol", "ASC"]],
      raw: true,
    });
    
    const positions = await this.getOptionPositions({ userId });
    const orders = await this.getOptionOrders({ userId });

    const wallet = await db.UserBalance.findOne({
      where: { userId},
      raw: true,
    });

    const openPremium = positions.reduce((sum, p) => {
      return sum + (Number(p.markPremium || 0) * Number(p.contracts || 0) * 100);
    }, 0);

    const openPnl = positions.reduce((sum, p) => sum + Number(p.pnl || 0), 0);

    return {
      optionsAssets: assets.map((a) => ({
        symbol: a.symbol,
        name: a.name,
        spotPrice: Number(a.price || 0),
        change: Number(a.change || 0),
        high24h: Number(a.high24h || 0),
        low24h: Number(a.low24h || 0),
      })),
      openOptionPositions: positions,
      optionOrders: orders,
      accountSummary: {
        balance: `$${Number(wallet?.balance || 0).toFixed(2)}`,
        buyingPower: `$${Number(wallet?.balance || 0).toFixed(2)}`,
        usedMargin: "$0.00",
        openPremium: `$${openPremium.toFixed(2)}`,
        ivRank: "42%",
        openPnl: `${openPnl >= 0 ? "+" : ""}$${openPnl.toFixed(2)}`,
      },
    };
  }

  async tradeOption({ userId, payload }) {
    const {
      symbol,
      expiry,
      strike,
      optionType,
      side,
      orderType,
      contracts,
      premium,
      stopLoss = 0,
      takeProfit = 0,
    } = payload;

    if (!symbol || !expiry || !strike || !optionType || !side || !orderType) {
      throw new Error("Missing required fields");
    }

    if (!["call", "put"].includes(optionType)) {
      throw new Error("Invalid option type");
    }

    if (!["buy", "sell"].includes(side)) {
      throw new Error("Invalid side");
    }

    if (!["market", "limit"].includes(orderType)) {
      throw new Error("Invalid order type");
    }

    const qty = Number(contracts);
    const optionPremium = Number(premium);
    const strikePrice = Number(strike);

    if (!qty || qty < 1) throw new Error("Invalid contracts");
    if (Number.isNaN(optionPremium) || optionPremium < 0) throw new Error("Invalid premium");
    if (Number.isNaN(strikePrice) || strikePrice <= 0) throw new Error("Invalid strike");

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

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

    const wallet = await db.UserBalance.findOne({
      where: { userId},
    });

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

    const chainRow = await OptionChain.findOne({
      where: {
        symbol: asset.symbol,
        expiry,
        strike: strikePrice,
      },
      raw: true,
    });

    if (!chainRow) {
      throw new Error("Option contract not found");
    }

    const multiplier = 100;
    const totalPremium = qty * optionPremium * multiplier;
    const shouldFillImmediately = orderType === "market";

    return await sequelize.transaction(async (transaction) => {
      let reservedAmount = 0;

      if (side === "buy") {
        reservedAmount = totalPremium;
      } else {
        reservedAmount = totalPremium * 1.5;
      }

      if (Number(wallet.balance) < reservedAmount) {
        throw new Error("Insufficient balance");
      }

      wallet.balance = Number(wallet.balance) - reservedAmount;
      await wallet.save({ transaction });

      const order = await OptionOrder.create(
        {
          userId,
          assetId: asset.id,
          symbol: asset.symbol,
          expiry,
          strike: strikePrice,
          optionType,
          side,
          orderType,
          contracts: qty,
          premium: optionPremium,
          stopLoss: Number(stopLoss || 0),
          takeProfit: Number(takeProfit || 0),
          status: shouldFillImmediately ? "filled" : "pending",
          filledAt: shouldFillImmediately ? new Date() : null,
          totalCost: totalPremium,
          reservedAmount,
        },
        { transaction }
      );

      if (!shouldFillImmediately) {
        return { order };
      }

      const existing = await OptionPosition.findOne({
        where: {
          userId,
          symbol: asset.symbol,
          expiry,
          strike: strikePrice,
          optionType,
          side,
          status: "open",
        },
        transaction,
      });

      if (existing) {
        const oldContracts = Number(existing.contracts);
        const oldPremium = Number(existing.entryPremium);
        const totalContracts = oldContracts + qty;

        existing.contracts = totalContracts;
        existing.entryPremium =
          ((oldContracts * oldPremium) + (qty * optionPremium)) / totalContracts;
        existing.markPremium = optionPremium;
        existing.stopLoss = Number(stopLoss || existing.stopLoss || 0);
        existing.takeProfit = Number(takeProfit || existing.takeProfit || 0);

        await existing.save({ transaction });

        return {
          order,
          position: existing,
        };
      }

      const position = await OptionPosition.create(
        {
          userId,
          assetId: asset.id,
          symbol: asset.symbol,
          expiry,
          strike: strikePrice,
          optionType,
          side,
          contracts: qty,
          entryPremium: optionPremium,
          markPremium: optionPremium,
          stopLoss: Number(stopLoss || 0),
          takeProfit: Number(takeProfit || 0),
          status: "open",
          openedAt: new Date(),
        },
        { transaction }
      );

      return {
        order,
        position,
      };
    });
  }

  async getOptionChain({ symbol }) {
    const upperSymbol = String(symbol).toUpperCase();

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

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

    const rows = await OptionChain.findAll({
      where: { symbol: upperSymbol },
      order: [
        ["expiry", "ASC"],
        ["strike", "ASC"],
      ],
      raw: true,
    });

    const expiries = [...new Set(rows.map((r) => r.expiry))];
    const grouped = {};

    expiries.forEach((expiry) => {
      grouped[expiry] = rows
        .filter((r) => r.expiry === expiry)
        .map((r) => ({
          strike: Number(r.strike),
          callBid: Number(r.callBid),
          callAsk: Number(r.callAsk),
          putBid: Number(r.putBid),
          putAsk: Number(r.putAsk),
          callIv: Number(r.callIv || 0),
          putIv: Number(r.putIv || 0),
          openInterest: Number(r.openInterest || 0),
          volume: Number(r.volume || 0),
        }));
    });

    return {
      symbol: asset.symbol,
      spotPrice: Number(asset.price || 0),
      expiries,
      ...grouped,
    };
  }

  async getOptionPositions({ userId }) {
    const positions = await OptionPosition.findAll({
      where: { userId, status: "open" },
      order: [["createdAt", "DESC"]],
      raw: true,
    });

    return positions.map((p) => {
      const pnl =
        (Number(p.markPremium) - Number(p.entryPremium)) *
        Number(p.contracts) *
        100 *
        (p.side === "buy" ? 1 : -1);

      return {
        ...p,
        pnl,
      };
    });
  }

  async getOptionOrders({ userId }) {
    const orders = await OptionOrder.findAll({
      where: {
        userId,
        status: {
          [Op.in]: ["pending", "filled"],
        },
      },
      order: [["createdAt", "DESC"]],
      raw: true,
    });

    return orders;
  }

  async closeOptionPosition({ userId, positionId }) {
    const position = await OptionPosition.findOne({
      where: {
        id: positionId,
        userId,
        status: "open",
      },
    });

    if (!position) {
      throw new Error("Position not found");
    }

    const wallet = await Wallet.findOne({
      where: { userId, currency: "USD" },
    });

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

    const realizedPnl =
      (Number(position.markPremium) - Number(position.entryPremium)) *
      Number(position.contracts) *
      100 *
      (position.side === "buy" ? 1 : -1);

    const creditBack =
      Number(position.markPremium) * Number(position.contracts) * 100;

    return await sequelize.transaction(async (transaction) => {
      wallet.balance = Number(wallet.balance) + creditBack;
      await wallet.save({ transaction });

      position.status = "closed";
      position.closedAt = new Date();
      position.realizedPnl = realizedPnl;
      await position.save({ transaction });

      await OptionOrder.create(
        {
          userId,
          assetId: position.assetId,
          symbol: position.symbol,
          expiry: position.expiry,
          strike: position.strike,
          optionType: position.optionType,
          side: position.side === "buy" ? "sell" : "buy",
          orderType: "market",
          contracts: position.contracts,
          premium: position.markPremium,
          stopLoss: 0,
          takeProfit: 0,
          status: "filled",
          filledAt: new Date(),
          totalCost: creditBack,
          reservedAmount: 0,
          parentPositionId: position.id,
        },
        { transaction }
      );

      return {
        position,
        realizedPnl,
        walletBalance: wallet.balance,
      };
    });
  }

  async cancelOptionOrder({ userId, orderId }) {
    const order = await OptionOrder.findOne({
      where: {
        id: orderId,
        userId,
        status: "pending",
      },
    });

    if (!order) {
      throw new Error("Pending order not found");
    }

    const wallet = await Wallet.findOne({
      where: { userId, currency: "USD" },
    });

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

    return await sequelize.transaction(async (transaction) => {
      wallet.balance = Number(wallet.balance) + Number(order.reservedAmount || 0);
      await wallet.save({ transaction });

      order.status = "cancelled";
      await order.save({ transaction });

      return {
        order,
        walletBalance: wallet.balance,
      };
    });
  }
}

module.exports = new OptionTradingService();