const { User, TradeHistory, sequelize, UserBalance, Transaction, TradeSymbol, UserPlan, Plan, Coupon, TraderProfile, CopyRelation } = require("../../models");
const { Op } = require("sequelize");
const { ValidationError, NotFoundError, BadRequestError, ConflictError } = require("../../utils/errors");
const { generateTransactionId, generateRandomString } = require("../../utils/helpers");

class adminUserService {
  async getUsers(query) {
    const { q, status, riskLevel, kycStatus, page = 1, limit = 20 } = query;
    const offset = (Number(page) - 1) * Number(limit);

    const where = {};
    if (status) where.status = status;
    if (riskLevel) where.riskLevel = riskLevel;
    if (kycStatus) where.kycStatus = kycStatus;

    if (q) {
      where[Op.or] = [
        { id: { [Op.like]: `%${q}%` } },
        { email: { [Op.like]: `%${q}%` } },
        { name: { [Op.like]: `%${q}%` } },
      ];
    }

    const { rows, count } = await User.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      limit: Number(limit),
      offset,
      include: [{ model: UserBalance, as: 'balance' }]
    });

    return {
      count,
      page: Number(page),
      limit: Number(limit),
      users: rows.map(u => ({
        id: u.id,
        user_id: u.userId,
        name: u.firstName + " "+ u.lastName,
        firstName: u.firstName,
        lastname: u.lastName,
        email: u.email,
        status: u.status,
        balance: u.balance?.balance,
        bonus: u.balance?.bonus,
        riskLevel: u.riskLevel,
        kycStatus: u.kycStatus,
        tier: u.tier,
        createdAt: u.created_at,
      })),
    };
  }

  async getUserById(userId) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");
    return { user };
  }

  async updateUser(userId, data) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");
    await user.update(data);
    return user;
  }

  async suspendUser(userId, data) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");
    await user.update({ status: "suspended", suspendReason: data?.reason ?? null });
    return user;
  }

  async holdUser(userId, data) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");
    await user.update({ status: "held", suspendReason: data?.reason ?? null });
    return user;
  }

  async disableUser(userId, data) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");
    await user.update({ status: "disabled", suspendReason: data?.reason ?? null });
    return { user, data: data ?? {} };
  }

  async unsuspendUser(userId) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");
    await user.update({ status: "active", suspendReason: null });
    return user;
  }

  async updateBalance(userId, data) {
    const { operation, type, amount } = data;
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const user_balance = await UserBalance.findOne({ where: { userId } });
    if (!user_balance) throw new NotFoundError("balance not found");

    const balance = Number(user_balance.balance);
    const bonus = Number(user_balance.bonus);
    const px = Number(amount ?? 0);
    if (type == 'Balance') {
      let newBalance = 0;
      if (operation == 'add') {
        newBalance = balance + px
      } else if (operation == 'subtract') {
        newBalance = balance - px
      } else if (operation == 'clear') {
        newBalance = 0
      }
      await user_balance.update({ balance: newBalance })
    } else if (type == 'Bonus') {
      let newBonus = 0;
      if (operation == 'add') {
        newBonus = bonus + px
      } else if (operation == 'subtract') {
        newBonus = bonus - px
      } else if (operation == 'clear') {
        newBonus = 0
      }
      await user_balance.update({ bonus: newBonus })
    }

    return { user, balance: user_balance }
  }

  async getUserTradeHistory(userId) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const trades = await TradeHistory.findAll({
      where: { userId },
      order: [["created_at", "DESC"]],
      limit: 200,
    });

    const total = trades.length;
    const won = trades.filter(t => String(t.result).toLowerCase() === "won").length;
    const loss = trades.filter(t => String(t.result).toLowerCase() === "loss").length;
    const pnl = trades.reduce((s, t) => s + Number(t.pnl ?? 0), 0);

    return {
      stats: {
        total,
        won,
        loss,
        winRate: total ? ((won / total) * 100).toFixed(2) : "0.00",
        pnl,
      },
      trades,
    };
  }

  async getTraderProfileByReference(traderId) {
    const profile = await TraderProfile.findOne({
      where: { reference: traderId },
    });

    if (!profile) {
      throw new NotFoundError('Trader profile not found');
    }

    return profile;
  }

  async addUserTradeHistory(userId, data, adminId) {
    const {
      asset,
      side,
      marketType = "market",
      limit,
      quantity,
      price,
      leverage,
      trader = null,
      tradedBy = "manual",
      duration,
      stopLoss,
      takeProfit
    } = data;

    // 1) Validate
    if (!asset) throw new BadRequestError("asset is required");
    if (!["buy", "sell"].includes(side)) throw new BadRequestError("side must be buy or sell");

    const qty = Number(quantity);
    if (!Number.isFinite(qty) || qty <= 0) throw new BadRequestError("quantity must be > 0");

    /*const px = Number(price);
    if (!Number.isFinite(px) || px <= 0) throw new BadRequestError("price must be > 0");*/
    const px = 123

    const lev = Number(leverage);
    if (!Number.isFinite(lev) || lev <= 0) throw new BadRequestError("leverage must be > 0");

    const dur = Number(duration);
    if (!Number.isFinite(dur) || dur <= 0) throw new BadRequestError("duration must be > 0");

    if (marketType === "limit") {
      const lim = Number(limit);
      if (!Number.isFinite(lim) || lim <= 0) {
        throw new BadRequestError("limit is required for limit orders");
      }
    }

    // 2) Asset exists + tradable
    const assetRow = await TradeSymbol.findOne({
      where: { symbol: asset, status: true },
    });
    if (!assetRow) throw new NotFoundError("Trading Symbol not found or not tradable");

    let traderProfile = {}

    if (trader) {
      traderProfile = await CopyRelation.findOne({
        where: { traderId: trader, userId, status: true },
      });
      if (!traderProfile) throw new NotFoundError("Invalid copy trader");
    }

    // 3) Anti-spam duplicate pending
    const existing = await TradeHistory.findOne({
      where: {
        userId,
        pairId: asset,
        side: side,
        status: { [Op.in]: ["pending", "open", "initiated", "processing"] },
        createdAt: { [Op.gte]: new Date(Date.now() - 30 * 1000) }, // last 30s
      },
      order: [["created_at", "DESC"]],
    });
    if (existing) {
      throw new BadRequestError("You already have a pending trade for this asset. Please wait.");
    }

    const t = await sequelize.transaction();
    try {
      const user = await User.findByPk(userId, { transaction: t, lock: t.LOCK.UPDATE });
      if (!user) throw new NotFoundError("User not found");

      // Lock user balance row
      const balance = await UserBalance.findOne({
        where: { userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });
      if (!balance) throw new ConflictError("User must have a wallet/balance");

      const currentBalance = Number(balance.balance ?? 0);
      if (currentBalance < qty) {
        throw new BadRequestError("Insufficient balance for this trade");
      }

      // Deduct margin (simple example)
      balance.balance = currentBalance - qty;
      await balance.save({ transaction: t });

      // Create transaction record (audit trail)
      const tx = await Transaction.create(
        {
          transactionId: generateTransactionId("OD"),
          userId,
          source: "funding",
          kind: "trade_margin",
          direction: "debit",
          toType: 'internal',
          currency: user.currency,
          amount: qty,
          status: "completed",
          description: `${side.toUpperCase()} ${qty} ${asset} @ ${px} (x${lev})`,
          metadata: {
            asset,
            side,
            marketType,
            limit: marketType === "limit" ? Number(limit) : null,
            quantity: qty,
            price: px,
            leverage: lev,
            duration: dur,
          },
        },
        { transaction: t }
      );

      // Create trade history (your table)
      const trade = await TradeHistory.create(
        {
          orderId: tx.transactionId,
          userId,
          pairId: assetRow.id,
          side,
          orderType: marketType,
          volume: qty,
          tradedBy,
          traderId: traderProfile?.trader_id,
          entryPrice: px,
          requestedPrice: limit,
          leverage: lev,
          duration: dur,
          takeProfit: takeProfit ?? null,
          stopLoss: stopLoss ?? null,
          status: "open",
        },
        { transaction: t }
      );

      await t.commit();

      const trades = await TradeHistory.findAll({
        where: { userId },
        order: [["created_at", "DESC"]],
        limit: 200,
      });

      return { user, tx, trade, trades };
    } catch (e) {
      await t.rollback();
      throw e;
    }
  }

  async getUserPlanHistory(userId) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const plans = await Plan.findAll({
      order: [["created_at", "DESC"]],
    });

    const userPlans = await UserPlan.findAll({
      where: { userId },
      order: [["created_at", "DESC"]],
      limit: 200,
    });

    const total = userPlans.length;
    const active = userPlans.filter(t => String(t.status).toLowerCase() === "active").length;

    return {
      stats: {
        total,
        active,
      },
      plans,
      userPlans
    };
  }

  async addUserPlanHistory(userId, data) {
    const { planId, coupon, amount } = data;

    const amountNum = Number(amount);
    if (!Number.isFinite(amountNum) || amountNum <= 0) {
      throw new ValidationError("Invalid amount");
    }

    return await sequelize.transaction(async (t) => {

      const user = await User.findByPk(userId, { transaction: t, lock: t.LOCK.UPDATE });
      if (!user) throw new NotFoundError("User not found");

      // 1) Lock user balance row to avoid double-spend
      const balance = await UserBalance.findOne({
        where: { userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });

      if (!balance) {
        throw new ConflictError("User must have a user balance");
      }

      // 2) Get plan
      const plan = await Plan.findOne({
        where: { id: planId },
        transaction: t,
      });

      if (!plan) {
        throw new ConflictError("Plan does not exist");
      }

      const currentBalance = Number(balance.balance ?? 0);
      const minPrice = Number(plan.price ?? 0);

      // maxPrice can be 'Unlimited' (string) or numeric string
      const hasMax =
        plan.maxPrice && String(plan.maxPrice).toLowerCase() !== "unlimited";
      const maxPrice = hasMax ? Number(plan.maxPrice) : null;

      if (amountNum > currentBalance) {
        throw new ValidationError("Insufficient balance");
      }

      if (amountNum < minPrice) {
        throw new ValidationError("Amount is less than the minimum for this plan");
      }

      if (hasMax && Number.isFinite(maxPrice) && amountNum > maxPrice) {
        throw new ValidationError("Amount is greater than the maximum for this plan");
      }

      // 3) Optional: validate coupon (only if provided)
      // NOTE: adapt "name" field if you store coupon code differently.
      let discountApplied = 0;
      let finalAmount = amountNum;

      if (coupon) {
        const c = await Coupon.findOne({
          where: {
            name: coupon,
            status: "active",
            type: { [Op.in]: ["all", "plan"] },
          },
          transaction: t,
          lock: t.LOCK.UPDATE,
        });

        if (!c) {
          throw new ValidationError("Invalid or inactive coupon");
        }

        // Basic "plan applicability" example. Adjust to your rules.
        // e.g. applicable: { plans:["basic","pro"] } OR { planIds:[...] }
        const applicable = c.applicable || {};
        if (c.type === "plan") {
          const planIds = applicable.plans || [];
          const planNames = applicable.plans || [];
          const planOk =
            (Array.isArray(planIds) && planIds.includes(plan.id)) ||
            (Array.isArray(planNames) &&
              planNames.map(String).includes(String(plan.name).toLowerCase()));

          if (!planOk && (planIds.length || planNames.length)) {
            throw new ValidationError("Coupon not applicable to this plan");
          }
        }

        if (c.discount_type === "percent") {
          discountApplied = (finalAmount * Number(c.discount || 0)) / 100;
        } else {
          discountApplied = Number(c.discount || 0);
        }

        // prevent negative / over-discount
        if (discountApplied < 0) discountApplied = 0;
        if (discountApplied > finalAmount) discountApplied = finalAmount;

        finalAmount = finalAmount - discountApplied;

        // Ensure final amount still respects plan minimum (optional rule)
        if (finalAmount < minPrice) {
          throw new ValidationError("Discount makes amount below plan minimum");
        }
      }
      const tx = await Transaction.create(
        {
          transactionId: generateTransactionId("INV"),
          userId,
          source: "funding",
          kind: "investment",
          direction: "debit",
          toType: 'internal',
          currency: user.currency,
          amount: amountNum,
          status: "completed",
          description: `Purchased ${plan.name} plan`,
          metadata: {
            planId,
            planName: plan.name,
            coupon: coupon || null,
            discount_applied: discountApplied,
            original_amount: amountNum,

          },
        },
        { transaction: t }
      );
      // 4) Create user plan
      const createdPlan = await UserPlan.create(
        {
          planId,
          userId,
          reference: tx.transactionId,
          amount: amountNum, // amount AFTER discount (change to amountNum if you want original)
          status: "active",
          roi: plan.roi,
          investment_duration: {
            duration: plan.duration,
            period: plan.durationType, // make sure field name matches your model (durationType vs duration_type)
          },
          metadata: {
            roi_type: plan.roiType,
            roi_interval: plan.roiInterval,
            coupon: coupon || null,
            discount_applied: discountApplied,
            original_amount: amountNum,
          },
          activated_at: new Date(),
        },
        { transaction: t }
      );

      // 5) Update balance
      await balance.update(
        { balance: currentBalance - amountNum }, // debit original amount (or finalAmount if you only want to debit discounted)
        { transaction: t }
      );

      // 6) Optional: mark coupon used (ONLY if your coupon is single-use)
      // await c.update({ status: "used" }, { transaction: t });
      const plans = this.getUserPlanHistory(userId)
      return { user, createdPlan, transaction: tx, plans };
    });
  }

  async addUserRoiHistory(userId, data) {
    const { planId, amount } = data;

    const amountNum = Number(amount);
    if (!Number.isFinite(amountNum) || amountNum <= 0) {
      throw new ValidationError("Invalid amount");
    }

    return await sequelize.transaction(async (t) => {

      const user = await User.findByPk(userId, { transaction: t, lock: t.LOCK.UPDATE });
      if (!user) throw new NotFoundError("User not found");

      const userplan = await User.findOne({ where: { userId, id: planId }, include: [{ model: Plan, as: 'plan' }] }, { transaction: t, lock: t.LOCK.UPDATE });
      if (!userplan) throw new NotFoundError("UserPlan not found");

      // 1) Lock user balance row to avoid double-spend
      const balance = await UserBalance.findOne({
        where: { userId },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });

      if (!balance) {
        throw new ConflictError("User must have a user balance");
      }


      const currentBalance = Number(balance.balance ?? 0);

      const tx = await Transaction.create(
        {
          transactionId: generateTransactionId("ROI"),
          userId,
          source: "funding",
          kind: "roi",
          direction: "credit",
          toType: 'internal',
          currency: user.currency,
          amount: amountNum,
          status: "completed",
          description: `Roi on  ${userplan.plan?.name} plan with id:${userplan.reference}`,
        },
        { transaction: t }
      );
      // 4) Create user plan

      const currentRoi = Number(userplan.totalReturn ?? 0)

      await userplan.update(
        { totalReturn: currentRoi + amountNum },
        { transaction: t }
      )
      // 5) Update balance
      await balance.update(
        { balance: currentBalance + amountNum }, // debit original amount (or finalAmount if you only want to debit discounted)
        { transaction: t }
      );

      // 6) Optional: mark coupon used (ONLY if your coupon is single-use)
      // await c.update({ status: "used" }, { transaction: t });

      return { user, userPlan: userplan, transaction: tx };
    });
  }
  
  async getUserTraderHistory(userId) {
    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const copytraders = await TraderProfile.findAll({
      order: [["created_at", "DESC"]],
    });

    const mytraders = await CopyRelation.findAll({
      where: { userId },
      include: [{ model: TraderProfile, as: 'traderProfile' }],
      order: [["created_at", "DESC"]],
      limit: 200,
    });

    const total = copytraders.length;
    //const active = userPlans.filter(t => String(t.status).toLowerCase() === "active").length;

    return {
      stats: {
        total,
        // active,
      },
      copytraders,
      mytraders
    };
  }

  async startCopying(userId, traderId, settings) {
    const { copyAmount, copyMode, maxPerTrade, stopLossPercent, takeProfitPercent } = settings;

    const trader = await this.getTraderProfileByReference(traderId);

    if (trader.userId === userId) {
      throw new BadRequestError('Cannot copy yourself');
    }

    if (!trader.acceptingCopiers) {
      throw new BadRequestError('Trader is not accepting copiers');
    }

    if (copyAmount < parseFloat(trader.minCopyAmount)) {
      throw new BadRequestError(`Minimum copy amount is ${trader.minCopyAmount}`);
    }

    // Check if already copying
    const existing = await CopyRelation.findOne({
      where: { copierId: userId, traderProfileId: trader.id, type: 'copy', status: { [Op.ne]: 'stopped' } },
    });

    if (existing) {
      throw new BadRequestError('Already copying this trader');
    }

    const relation = await CopyRelation.create({
      copierId: userId,
      traderProfileId: trader.id,
      type: 'copy',
      status: 'active',
      copyAmount,
      copyMode: copyMode || 'proportional',
      maxPerTrade,
      stopLossPercent,
      takeProfitPercent,
      invested: copyAmount,
      currentValue: copyAmount,
    });

    // Update copier count
    await trader.update({ totalCopiers: trader.totalCopiers + 1 });

    return relation;
  }

  async stopCopying(userId, relationId) {
    const relation = await CopyRelation.findOne({
      where: { id: relationId, copierId: userId, type: 'copy' },
      include: [{ model: TraderProfile, as: 'traderProfile' }],
    });

    if (!relation) {
      throw new NotFoundError('Copy relation not found');
    }

    await relation.update({
      status: 'stopped',
      stoppedAt: new Date(),
    });

    // Update copier count
    await relation.traderProfile.update({
      totalCopiers: Math.max(0, relation.traderProfile.totalCopiers - 1),
    });

    return relation;
  }


}

module.exports = new adminUserService();
