"use strict";

const { Op } = require("sequelize");
const { sequelize, Signal, SignalProvider, SignalSubscription, User, MarketPair, UserBalance } = require("../models");
const signalEvents = require("../events/signalEvents");
const {
  BadRequestError,
  NotFoundError,
  ConflictError,
  ForbiddenError,
} = require("../utils/errors");
const tradeService = require("./tradeService");
const forexService = require("./forexService");
const spotService = require("./spotService");
const optionsService = require("./optionsService");
const { getUploadedFile } = require("../utils/helpers");

class SignalService {
  toNumber(value, fallback = 0) {
    const n = Number(value);
    return Number.isFinite(n) ? n : fallback;
  }

  parseArray(value) {
    if (Array.isArray(value)) return value;
    if (!value) return [];
    if (typeof value === "string") {
      try {
        const parsed = JSON.parse(value);
        return Array.isArray(parsed) ? parsed : [];
      } catch {
        return value.split(",").map((x) => x.trim()).filter(Boolean);
      }
    }
    return [];
  }

  confidenceToPct(confidence) {
    const v = String(confidence || "").toLowerCase();
    if (v === "high") return 90;
    if (v === "medium") return 70;
    if (v === "low") return 45;
    return 60;
  }

  normalizeProvider(provider, isSubscribed = false) {
    const p = provider?.toJSON ? provider.toJSON() : provider;

    return {
      id: p.id,
      userId: p.userId,
      provider: p.provider,
      name: p.name || p.provider || "Unknown",
      description: p.description || "",
      avatar: getUploadedFile(p.avatar || "", "signals"),
      verified: !!p.verified,
      rating: this.toNumber(p.rating, 0),
      totalSignals: this.toNumber(p.totalSignals, 0),
      successfulSignals: this.toNumber(p.successfulSignals, 0),
      winRate: this.toNumber(p.winRate, 0),
      avgProfitPercent: this.toNumber(p.avgProfitPercent, 0),
      totalProfit: this.toNumber(p.totalProfit, 0),
      subscribers: this.toNumber(p.subscribers, 0),
      price: this.toNumber(p.price, 0),
      pricePeriod: p.pricePeriod || "month",
      tier: p.tier || "basic",
      specialties: this.parseArray(p.specialties),
      supportedPairs: this.parseArray(p.supportedPairs),
      tradingStyle: p.tradingStyle || "",
      experience: p.experience || "",
      status: p.status || "active",
      isPublic: p.isPublic !== false,
      isFeatured: !!p.isFeatured,
      telegramChannel: p.telegramChannel || "",
      discordServer: p.discordServer || "",
      website: p.website || "",
      performanceData: Array.isArray(p.performanceData) ? p.performanceData : [],
      isSubscribed,
    };
  }

  normalizeSignal(signal) {
    const s = signal?.toJSON ? signal.toJSON() : signal;
    const provider = s.provider || {};

    const rawType = String(s.type || "").toLowerCase();
    const type = rawType === "buy" ? "long" : rawType === "sell" ? "short" : rawType || "long";

    const takeProfit = [s.takeProfit1, s.takeProfit2, s.takeProfit3]
      .filter((x) => x !== null && x !== undefined)
      .map((x) => this.toNumber(x));

    return {
      id: s.id,
      providerId: s.providerId,
      provider: {
        id: provider.id,
        name: provider.name || provider.provider || "Provider",
        avatar: getUploadedFile(provider.avatar || "", "signals"),
        verified: !!provider.verified,
      },
      pair: s.pair,
      pairSymbol: s.pairs?.symbol || "",
      market: s.marketType || "forex",
      marketType: s.marketType || "forex",
      marketEntry: s.marketEntry || "market",
      type,
      status: s.status || "active",
      entryPrice: this.toNumber(s.entryPrice, 0),
      currentPrice: this.toNumber(s.currentPrice ?? s.entryPrice, 0),
      exitPrice: s.exitPrice != null ? this.toNumber(s.exitPrice, 0) : null,
      stopLoss: s.stopLoss != null ? this.toNumber(s.stopLoss, 0) : null,
      takeProfit,
      takeProfit1: s.takeProfit1 != null ? this.toNumber(s.takeProfit1, 0) : null,
      takeProfit2: s.takeProfit2 != null ? this.toNumber(s.takeProfit2, 0) : null,
      takeProfit3: s.takeProfit3 != null ? this.toNumber(s.takeProfit3, 0) : null,
      leverage: this.toNumber(s.leverage, 1),
      timeframe: s.timeframe || "1H",
      profitLoss: this.toNumber(s.profitLoss, 0),
      profitLossPercent: this.toNumber(s.profitLossPercent, 0),
      riskRewardRatio: s.riskRewardRatio != null ? this.toNumber(s.riskRewardRatio, 0) : null,
      analysis: s.analysis || "",
      imageUrl: s.imageUrl || "",
      tags: this.parseArray(s.tags),
      confidence: s.confidence || "medium",
      confidencePct: this.confidenceToPct(s.confidence),
      closedAt: s.closedAt || null,
      expiresAt: s.expiresAt || null,
      subscribersTaken: this.toNumber(s.subscribersTaken, 0),
      successfulTrades: this.toNumber(s.successfulTrades, 0),
      createdAt: s.createdAt,
      updatedAt: s.updatedAt,
    };
  }

  async getProviderByUserId(userId, transaction = null) {
    const provider = await SignalProvider.findOne({
      where: {
        userId,
        status: {
          [Op.ne]: "suspended",
        },
      },
      transaction,
    });

    if (!provider) {
      throw new NotFoundError("Signal provider account not found");
    }

    return provider;
  }

  async getActiveSubscriberUserIds(providerId, transaction = null) {
    const subscriptions = await SignalSubscription.findAll({
      where: {
        providerId,
        status: "active",
      },
      attributes: ["userId"],
      transaction,
    });

    return subscriptions.map((x) => Number(x.userId));
  }

  async getSubscribedProviderIds(userId) {
    const rows = await SignalSubscription.findAll({
      where: {
        userId,
        status: "active",
      },
      attributes: ["providerId"],
    });

    return rows.map((r) => Number(r.providerId));
  }

  async getProviderDirectory(userId, { page = 1, limit = 24 } = {}) {
    const offset = (page - 1) * limit;
    const subscribedIds = await this.getSubscribedProviderIds(userId);

    const { rows, count } = await SignalProvider.findAndCountAll({
      where: {
        status: "active",
        isPublic: true,
      },
      order: [
        ["verified", "DESC"],
        ["isFeatured", "DESC"],
        ["subscribers", "DESC"],
        ["winRate", "DESC"],
        ["createdAt", "DESC"],
      ],
      limit,
      offset,
    });

    return {
      providers: rows.map((p) => this.normalizeProvider(p, subscribedIds.includes(Number(p.id)))),
      total: count,
      page,
      limit,
    };
  }

  async getMySubscriptions(userId, { page = 1, limit = 100 } = {}) {
    const offset = (page - 1) * limit;

    const { rows, count } = await SignalSubscription.findAndCountAll({
      where: { userId },
      include: [
        {
          model: SignalProvider,
          as: "provider",
          required: true,
        },
      ],
      order: [
        ["status", "ASC"],
        ["createdAt", "DESC"],
      ],
      limit,
      offset,
    });

    const subscriptions = rows.map((row) => {
      const sub = row.toJSON();
      return {
        id: sub.id,
        providerId: sub.providerId,
        userId: sub.userId,
        status: sub.status,
        startDate: sub.startDate,
        endDate: sub.endDate,
        autoRenew: !!sub.autoRenew,
        price: this.toNumber(sub.price, 0),
        pricePeriod: sub.pricePeriod || "month",
        notificationsEnabled: !!sub.notificationsEnabled,
        autoCopyEnabled: !!sub.autoCopyEnabled,
        autoCopyConfig: sub.autoCopyConfig || {},
        signalsTaken: this.toNumber(sub.signalsTaken, 0),
        profitLoss: this.toNumber(sub.profitLoss, 0),
        rating: sub.rating,
        review: sub.review,
        reviewedAt: sub.reviewedAt,
        provider: this.normalizeProvider(sub.provider, sub.status === "active"),
      };
    });

    const active = subscriptions.filter((x) => x.status === "active").length;

    return {
      subscriptions,
      stats: {
        total: count,
        active,
      },
      page,
      limit,
    };
  }

  async getMyFeed(userId, { page = 1, limit = 50, historyOnly = false } = {}) {
    const offset = (page - 1) * limit;
    const providerIds = await this.getSubscribedProviderIds(userId);

    if (!providerIds.length) {
      return {
        signals: [],
        total: 0,
        page,
        limit,
      };
    }

    const where = {
      providerId: { [Op.in]: providerIds },
      status: historyOnly
        ? { [Op.in]: ["closed", "cancelled", "expired"] }
        : "active",
    };

    const { rows, count } = await Signal.findAndCountAll({
      where,
      include: [
        {
          model: SignalProvider,
          as: "provider",
          required: false,
        },
        {
          model: MarketPair,
          as: "pairs",
          required: true,
        }
      ],
      order: [
        ["createdAt", "DESC"],
      ],
      limit,
      offset,
    });

    return {
      signals: rows.map((s) => this.normalizeSignal(s)),
      total: count,
      page,
      limit,
    };
  }

  async getUserStats(userId) {
    const subscriptions = await SignalSubscription.findAll({
      where: { userId },
      attributes: ["signalsTaken", "status", "profitLoss"],
    });

    const providerIds = subscriptions
      .filter((x) => x.status === "active")
      .map((x) => Number(x.providerId))
      .filter(Boolean);

    const activeSignals = providerIds.length
      ? await Signal.count({
        where: {
          providerId: { [Op.in]: providerIds },
          status: "active",
        },
      })
      : 0;

    const subscribedProviders = subscriptions.filter((x) => x.status === "active").length;
    const signalsCopied = subscriptions.reduce(
      (sum, x) => sum + this.toNumber(x.signalsTaken, 0),
      0
    );
    const totalProfit = subscriptions.reduce(
      (sum, x) => sum + this.toNumber(x.profitLoss, 0),
      0
    );

    let winRate = 0;
    if (providerIds.length) {
      const closedSignals = await Signal.findAll({
        where: {
          providerId: { [Op.in]: providerIds },
          status: "closed",
        },
        attributes: ["profitLossPercent"],
      });

      const pnlList = closedSignals.map((x) => this.toNumber(x.profitLossPercent, 0));
      const wins = pnlList.filter((x) => x > 0).length;
      winRate = pnlList.length ? Number(((wins / pnlList.length) * 100).toFixed(2)) : 0;
    }

    return {
      activeSignals,
      totalProfit: Number(totalProfit.toFixed(2)),
      winRate,
      subscribedProviders,
      signalsCopied,
    };
  }

  async subscribe(userId, providerId) {
    return sequelize.transaction(async (transaction) => {
      const provider = await SignalProvider.findOne({
        where: {
          id: providerId,
          status: "active",
        },
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!provider) {
        throw new NotFoundError("Provider not found");
      }

      let subscription = await SignalSubscription.findOne({
        where: { userId, providerId },
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (subscription && subscription.status === "active") {
        throw new ConflictError("You are already subscribed to this provider");
      }






      if (subscription) {


        await subscription.update(
          {
            status: "active",
            autoRenew: true,
            notificationsEnabled: true,
            price: provider.price || 0,
            pricePeriod: provider.pricePeriod || "month",
            startDate: new Date(),
            endDate: null,
          },
          { transaction }
        );
      } else {
        const balance = await UserBalance.findOne({
          where: { userId },
          transaction,
          lock: transaction.LOCK.UPDATE,
        });

        if (!balance) {
          throw new NotFoundError("UserBalance not found");
        }
        const uBalance = Number(balance.balance ?? 0)
        const subAmount = Number(provider.price ?? 0)
        if (uBalance < subAmount) {
          throw new NotFoundError("Insufficient Balance");
        }
        await balance.update({
          balance: uBalance - subAmount
        },
          { transaction }
        )
        subscription = await SignalSubscription.create(
          {
            userId,
            providerId,
            status: "active",
            startDate: new Date(),
            autoRenew: true,
            notificationsEnabled: true,
            autoCopyEnabled: false,
            price: provider.price || 0,
            pricePeriod: provider.pricePeriod || "month",
          },
          { transaction }
        );
      }

      await provider.increment("subscribers", { by: 1, transaction });

      const stats = await this.getUserStats(userId);

      signalEvents.emitToUser(userId, "subscription.updated", {
        action: "subscribe",
        providerId: Number(providerId),
        provider: this.normalizeProvider(provider, true),
        stats,
      });

      return {
        provider: this.normalizeProvider(provider, true),
        subscription: subscription.toJSON(),
        stats,
      };
    });
  }

  async unsubscribe(userId, providerId) {
    return sequelize.transaction(async (transaction) => {
      const subscription = await SignalSubscription.findOne({
        where: {
          userId,
          providerId,
          status: "active",
        },
        include: [
          {
            model: SignalProvider,
            as: "provider",
            required: false,
          },
        ],
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!subscription) {
        throw new NotFoundError("Active subscription not found");
      }

      await subscription.update(
        {
          status: "cancelled",
          autoRenew: false,
          endDate: new Date(),
        },
        { transaction }
      );

      if (subscription.provider) {
        const currentCount = this.toNumber(subscription.provider.subscribers, 0);
        await subscription.provider.update(
          {
            subscribers: Math.max(0, currentCount - 1),
          },
          { transaction }
        );
      }

      const stats = await this.getUserStats(userId);

      signalEvents.emitToUser(userId, "subscription.updated", {
        action: "unsubscribe",
        providerId: Number(providerId),
        stats,
      });

      return {
        providerId: Number(providerId),
        stats,
      };
    });
  }

  async buySignal(userId, signalId) {
    return sequelize.transaction(async (transaction) => {
      const signal = await Signal.findOne({
        where: { id: signalId },
        include: [{ model: SignalProvider, as: "provider", required: false }],
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!signal) {
        throw new NotFoundError("Signal not found");
      }

      if (signal.status !== "active") {
        throw new BadRequestError("Only active signals can be bought");
      }

      const subscription = await SignalSubscription.findOne({
        where: {
          userId,
          providerId: signal.providerId,
          status: "active",
        },
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!subscription) {
        throw new BadRequestError("Subscribe to this provider before buying a signal");
      }

      await signal.increment("subscribersTaken", { by: 1, transaction });
      await subscription.increment("signalsTaken", { by: 1, transaction });

      await signal.reload({
        transaction,
        include: [{ model: SignalProvider, as: "provider", required: false }],
      });

      await subscription.reload({ transaction });

      const stats = await this.getUserStats(userId);

      signalEvents.emitToUser(userId, "signal.bought", {
        signal: this.normalizeSignal(signal),
        providerId: Number(signal.providerId),
        signalsTaken: this.toNumber(subscription.signalsTaken, 0),
        stats,
      });

      return {
        signal: this.normalizeSignal(signal),
        signalsTaken: this.toNumber(subscription.signalsTaken, 0),
        stats,
      };
    });
  }

  async stopAll(userId) {
    return sequelize.transaction(async (transaction) => {
      const activeSubscriptions = await SignalSubscription.findAll({
        where: {
          userId,
          status: "active",
        },
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!activeSubscriptions.length) {
        const stats = await this.getUserStats(userId);
        return { stopped: 0, providerIds: [], stats };
      }

      const providerIds = activeSubscriptions.map((x) => Number(x.providerId));

      await SignalSubscription.update(
        {
          status: "paused",
          autoRenew: false,
          autoCopyEnabled: false,
        },
        {
          where: {
            userId,
            status: "active",
          },
          transaction,
        }
      );

      const providers = await SignalProvider.findAll({
        where: {
          id: { [Op.in]: providerIds },
        },
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      for (const provider of providers) {
        const current = this.toNumber(provider.subscribers, 0);
        await provider.update(
          {
            subscribers: Math.max(0, current - 1),
          },
          { transaction }
        );
      }

      const stats = await this.getUserStats(userId);

      signalEvents.emitToUser(userId, "signals.stopped_all", {
        stopped: activeSubscriptions.length,
        providerIds,
        stats,
      });

      return {
        stopped: activeSubscriptions.length,
        providerIds,
        stats,
      };
    });
  }

  async createSignal(providerUserId, payload) {
    return sequelize.transaction(async (transaction) => {
      const provider = await this.getProviderByUserId(providerUserId, transaction);

      const signal = await Signal.create(
        {
          providerId: provider.id,
          pair: payload.pair,
          marketType: payload.marketType,
          type: payload.type,
          marketEntry: payload.marketEntry,
          status: "active",
          entryPrice: payload.entryPrice,
          currentPrice: payload.currentPrice ?? payload.entryPrice,
          stopLoss: payload.stopLoss ?? null,
          takeProfit1: payload.takeProfit1 ?? null,
          takeProfit2: payload.takeProfit2 ?? null,
          takeProfit3: payload.takeProfit3 ?? null,
          leverage: payload.leverage ?? 1,
          timeframe: payload.timeframe ?? "1H",
          analysis: payload.analysis ?? "",
          imageUrl: payload.imageUrl ?? null,
          tags: payload.tags ?? [],
          confidence: payload.confidence ?? "medium",
          expiresAt: payload.expiresAt ?? null,
          riskRewardRatio: payload.riskRewardRatio ?? null,
          profitLoss: 0,
          profitLossPercent: 0,
          subscribersTaken: 0,
          successfulTrades: 0,
        },
        { transaction }
      );

      await provider.increment("totalSignals", { by: 1, transaction });

      await signal.reload({
        transaction,
        include: [{ model: SignalProvider, as: "provider", required: false }],
      });

      const subscriberUserIds = await this.getActiveSubscriberUserIds(provider.id, transaction);
      const normalizedSignal = this.normalizeSignal(signal);

      signalEvents.emitMany(subscriberUserIds, "signal.created", {
        signal: normalizedSignal,
        providerId: Number(provider.id),
      });

      return {
        signal: normalizedSignal,
      };
    });
  }

  async updateSignal(providerUserId, signalId, payload) {
    return sequelize.transaction(async (transaction) => {
      const provider = await this.getProviderByUserId(providerUserId, transaction);

      const signal = await Signal.findOne({
        where: {
          id: signalId,
          providerId: provider.id,
        },
        include: [{ model: SignalProvider, as: "provider", required: false }],
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!signal) {
        throw new NotFoundError("Signal not found");
      }

      if (signal.status !== "active") {
        throw new BadRequestError("Only active signals can be updated");
      }

      const updateData = {};
      const allowedFields = [
        "pair",
        "marketType",
        "type",
        "marketEntry",
        "entryPrice",
        "currentPrice",
        "stopLoss",
        "takeProfit1",
        "takeProfit2",
        "takeProfit3",
        "leverage",
        "timeframe",
        "analysis",
        "imageUrl",
        "tags",
        "confidence",
        "expiresAt",
        "riskRewardRatio",
      ];

      for (const key of allowedFields) {
        if (Object.prototype.hasOwnProperty.call(payload, key)) {
          updateData[key] = payload[key];
        }
      }

      await signal.update(updateData, { transaction });

      await signal.reload({
        transaction,
        include: [{ model: SignalProvider, as: "provider", required: false }],
      });

      const subscriberUserIds = await this.getActiveSubscriberUserIds(provider.id, transaction);
      const normalizedSignal = this.normalizeSignal(signal);

      signalEvents.emitMany(subscriberUserIds, "signal.updated", {
        signal: normalizedSignal,
        providerId: Number(provider.id),
      });

      return {
        signal: normalizedSignal,
      };
    });
  }

  async updateSignalPrice(providerUserId, signalId, currentPrice) {
    return sequelize.transaction(async (transaction) => {
      const provider = await this.getProviderByUserId(providerUserId, transaction);

      const signal = await Signal.findOne({
        where: {
          id: signalId,
          providerId: provider.id,
        },
        include: [{ model: SignalProvider, as: "provider", required: false }],
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!signal) {
        throw new NotFoundError("Signal not found");
      }

      if (signal.status !== "active") {
        throw new BadRequestError("Only active signals can be repriced");
      }

      const entry = this.toNumber(signal.entryPrice, 0);
      const nextCurrent = this.toNumber(currentPrice, 0);

      let percent = 0;
      if (entry > 0) {
        const isBuy = ["buy", "long"].includes(String(signal.type).toLowerCase());
        percent = isBuy
          ? ((nextCurrent - entry) / entry) * 100
          : ((entry - nextCurrent) / entry) * 100;
      }

      await signal.update(
        {
          currentPrice: nextCurrent,
          profitLossPercent: Number(percent.toFixed(2)),
        },
        { transaction }
      );

      await signal.reload({
        transaction,
        include: [{ model: SignalProvider, as: "provider", required: false }],
      });

      const subscriberUserIds = await this.getActiveSubscriberUserIds(provider.id, transaction);
      const normalizedSignal = this.normalizeSignal(signal);

      signalEvents.emitMany(subscriberUserIds, "signal.price_updated", {
        signal: normalizedSignal,
        providerId: Number(provider.id),
      });

      return {
        signal: normalizedSignal,
      };
    });
  }

  async closeSignal(providerUserId, signalId, payload = {}) {
    return sequelize.transaction(async (transaction) => {
      const provider = await this.getProviderByUserId(providerUserId, transaction);

      const signal = await Signal.findOne({
        where: {
          id: signalId,
          providerId: provider.id,
        },
        include: [{ model: SignalProvider, as: "provider", required: false }],
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!signal) {
        throw new NotFoundError("Signal not found");
      }

      if (signal.status !== "active") {
        throw new BadRequestError("Signal is not active");
      }

      const exitPrice =
        payload.exitPrice != null
          ? this.toNumber(payload.exitPrice, 0)
          : this.toNumber(signal.currentPrice ?? signal.entryPrice, 0);

      const entry = this.toNumber(signal.entryPrice, 0);
      const isBuy = ["buy", "long"].includes(String(signal.type).toLowerCase());

      let percent = 0;
      if (entry > 0) {
        percent = isBuy
          ? ((exitPrice - entry) / entry) * 100
          : ((entry - exitPrice) / entry) * 100;
      }

      const successful = percent > 0 ? 1 : 0;

      await signal.update(
        {
          status: "closed",
          exitPrice,
          currentPrice: exitPrice,
          profitLossPercent: Number(percent.toFixed(2)),
          profitLoss: payload.profitLoss ?? signal.profitLoss ?? 0,
          closedAt: new Date(),
          successfulTrades: this.toNumber(signal.successfulTrades, 0) + successful,
        },
        { transaction }
      );

      if (successful) {
        await provider.increment("successfulSignals", { by: 1, transaction });
      }

      const totalSignals = Math.max(1, this.toNumber(provider.totalSignals, 0));
      const successfulSignals = this.toNumber(provider.successfulSignals, 0) + successful;
      const winRate = Number(((successfulSignals / totalSignals) * 100).toFixed(2));

      await provider.update(
        {
          successfulSignals,
          winRate,
        },
        { transaction }
      );

      await signal.reload({
        transaction,
        include: [{ model: SignalProvider, as: "provider", required: false }],
      });

      const subscriberUserIds = await this.getActiveSubscriberUserIds(provider.id, transaction);
      const normalizedSignal = this.normalizeSignal(signal);

      signalEvents.emitMany(subscriberUserIds, "signal.closed", {
        signal: normalizedSignal,
        providerId: Number(provider.id),
      });

      return {
        signal: normalizedSignal,
      };
    });
  }

  async cancelSignal(providerUserId, signalId) {
    return sequelize.transaction(async (transaction) => {
      const provider = await this.getProviderByUserId(providerUserId, transaction);

      const signal = await Signal.findOne({
        where: {
          id: signalId,
          providerId: provider.id,
        },
        include: [{ model: SignalProvider, as: "provider", required: false }],
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!signal) {
        throw new NotFoundError("Signal not found");
      }

      if (signal.status !== "active") {
        throw new BadRequestError("Only active signals can be cancelled");
      }

      await signal.update(
        {
          status: "cancelled",
          closedAt: new Date(),
        },
        { transaction }
      );

      await signal.reload({
        transaction,
        include: [{ model: SignalProvider, as: "provider", required: false }],
      });

      const subscriberUserIds = await this.getActiveSubscriberUserIds(provider.id, transaction);
      const normalizedSignal = this.normalizeSignal(signal);

      signalEvents.emitMany(subscriberUserIds, "signal.cancelled", {
        signal: normalizedSignal,
        providerId: Number(provider.id),
      });

      return {
        signal: normalizedSignal,
      };
    });
  }

  /**
 * placeSignal
 *
 * Executes the trade implied by a signal on behalf of a user.
 * Routes to the correct sub-service based on signal.marketType.
 *
 * @param {number} userId
 * @param {number} signalId
 * @returns {{ signal: Signal, result: object }}
 */
  async placeSignal(userId, signalId) {
    // 1) Load user (needed for currency)
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    // 2) Load signal (no row-lock needed here — we're not mutating it)
    const signal = await Signal.findOne({
      where: { id: signalId, status: "active" },
    });
    if (!signal) throw new ConflictError("Signal not found or no longer active");



    // 3) Pull fields from signal (match actual model column names)
    const marketType = signal.marketType;   // "spot" | "forex" | "stock" | "terminal"
    const side = signal.type;         // "buy" | "sell" | "long" | "short"
    const pair = signal.pair;         // e.g. "EURUSD" or "BTC_USDT"
    const mPair = await MarketPair.findOne({ where: { id: pair } });
    if (!mPair) throw new ValidationError("MarketPair not found for given pair symbol");
    const entryPrice = Number(signal.entryPrice);
    const marketEntry = signal.marketEntry;  // "market" | "limit" | "stop"
    const stopLoss = signal.stopLoss != null ? Number(signal.stopLoss) : null;
    const takeProfit = signal.takeProfit1 != null ? Number(signal.takeProfit1) : null;
    const leverage = Number(signal.leverage ?? 1);

    let result = {};

    // 4) Route to the correct trading engine
    if (marketType === "spot") {
      const data = {
        symbol: mPair?.symbol,
        side: side === "long" ? "buy" : side === "short" ? "sell" : side,
        orderType: marketEntry,              // "market" | "limit" | "stop-limit"
        amount: 1,                        // spot uses qty in base units — caller may override
        price: marketEntry !== "market" ? entryPrice : undefined,
      };
      result = await spotService.tradeSpot(userId, data);

    } else if (marketType === "stock") {
      const data = {
        asset: mPair?.symbol,
        side: side === "long" ? "buy" : side === "short" ? "sell" : side,
        price: entryPrice,
        marketType: marketEntry,
        limit: marketEntry === "limit" ? entryPrice : null,
        quantity: 1,                       // default quantity; caller may pass through
      };
      result = await tradeService.tradeAsset(userId, user.currency, data);

    } else if (marketType === "terminal") {
      const data = {
        asset: mPair?.symbol,
        side: side === "long" ? "buy" : side === "short" ? "sell" : side,
        price: entryPrice,
        marketType: marketEntry,
        limit: marketEntry === "limit" ? entryPrice : null,
        quantity: 1,
        leverage,
        duration: 60,                      // default 60 s; extend as needed
        stopLoss,
        takeProfit,
      };
      result = await tradeService.tradeTerminal(userId, user.currency, data);

    } else if (marketType === "forex") {
      const payload = {
        asset: mPair?.symbol,
        side: side,                    // forexService accepts "buy"|"sell"|"long"|"short"
        marketType: marketEntry,
        lot: 0.01,                    // minimum lot; caller may override
        leverage,
        limit: marketEntry !== "market" ? entryPrice : null,
        stopLoss,
        takeProfit,
      };
      result = await forexService.placeTrade(userId, payload);

    } else if (marketType === "option" || marketType === "binary") {
      const data = {
        symbol: mPair?.symbol,
        direction: side === "long" ? "buy" : side === "short" ? "sell" : side,
        investment: 10,                 // default investment; caller may override
        durationSeconds: 300,               // 5 min default
        walletType: "live",
        price: entryPrice,
      };
      result = await optionsService.placeTrade(userId, data);

    } else {
      throw new BadRequestError(`Unsupported market type: ${marketType}`);
    }

    // 5) Increment the signal's subscribersTaken counter (fire-and-forget, non-critical)
    signal.increment("subscribersTaken").catch(() => { });

    return { signal, result };
  }

  async getMyProviderSignals(providerUserId, { page = 1, limit = 50, status } = {}) {
    const provider = await this.getProviderByUserId(providerUserId);
    const offset = (page - 1) * limit;

    const where = { providerId: provider.id };
    if (status) {
      where.status = status;
    }

    const { rows, count } = await Signal.findAndCountAll({
      where,
      include: [{ model: SignalProvider, as: "provider", required: false }],
      order: [["createdAt", "DESC"]],
      limit,
      offset,
    });

    return {
      signals: rows.map((x) => this.normalizeSignal(x)),
      total: count,
      page,
      limit,
    };
  }
}

module.exports = new SignalService();