const { Withdrawal, Transaction, Wallet, WalletBalance, UserBalance, sequelize } = require("../../models");
const { Op } = require("sequelize");
const { NotFoundError, ValidationError, BadRequestError, ConflictError } = require("../../utils/errors");

class adminWithdrawalsService {
  async getWithdrawals(source, query) {
    const { status, userId, reviewStatus, q, page = 1, limit = 20 } = query;
    const offset = (Number(page) - 1) * Number(limit);

    const whereTx = { kind: "withdrawal", source };
    if (status) whereTx.status = status;
    if (userId) whereTx.userId = userId;
    if (q) whereTx.transactionId = { [Op.like]: `%${q}%` };

    const whereWd = {};
    if (reviewStatus) whereWd.reviewStatus = reviewStatus;

    const { rows, count } = await Withdrawal.findAndCountAll({
      where: whereWd,
      include: [{
        model: Transaction,
        as: "transaction",
        where: whereTx,
      }],
      order: [["created_at", "DESC"]],
      limit: Number(limit),
      offset,
    });

    return {
      source,
      count,
      page: Number(page),
      limit: Number(limit),
      withdrawals: rows.map(w => ({
        id: w.id,
        tx: w.transaction?.transactionId,
        userId: w.transaction?.userId,
        amount: Number(w.transaction?.amount ?? 0),
        asset: w.asset,
        network: w.network,
        methodId: w.methodId,
        status: w.transaction?.status,
        reviewStatus: w.reviewStatus ?? "none",
        destination: w.destinationJson ?? {},
        createdAt: w.created_at,
      })),
    };
  }

  async holdWithdrawal(source, withdrawalId, data, adminId) {
    const note = data?.note ?? "Held for review";
    return await sequelize.transaction(async (t) => {
      const wd = await Withdrawal.findByPk(withdrawalId, { transaction: t, lock: t.LOCK.UPDATE, include: [{ model: Transaction, as: "transaction" }] });
      if (!wd) throw new NotFoundError("Withdrawal not found");

      const tx = wd.transaction;
      if (!tx || tx.kind !== "withdrawal" || tx.source !== source) throw new BadRequestError("Withdrawal source mismatch");
      if (tx.status !== "pending") throw new BadRequestError("Only pending withdrawals can be held");

      await wd.update({ reviewStatus: "held", reviewNote: note, reviewedBy: adminId, reviewedAt: new Date() }, { transaction: t });
      await tx.update({ status: "under_review", meta: { ...(tx.meta || {}), heldBy: adminId, heldAt: new Date(), note } }, { transaction: t });

      return { withdrawalId: wd.id, txId: tx.transactionId, status: "under_review" };
    });
  }

  async approveWithdrawal(source, withdrawalId, adminId) {
    return await sequelize.transaction(async (t) => {
      const wd = await Withdrawal.findByPk(withdrawalId, { transaction: t, lock: t.LOCK.UPDATE, include: [{ model: Transaction, as: "transaction" }] });
      if (!wd) throw new NotFoundError("Withdrawal not found");

      const tx = wd.transaction;
      if (!tx || tx.kind !== "withdrawal" || tx.source !== source) throw new BadRequestError("Withdrawal source mismatch");
      if (!["pending", "under_review"].includes(tx.status)) throw new BadRequestError("Withdrawal not actionable");

      // IMPORTANT:
      // We assume your user withdrawal request already LOCKED the funds (available -> locked).
      // If you DID NOT lock, then you must lock here before approving.

      await wd.update({ reviewStatus: "approved", approvedBy: adminId, approvedAt: new Date() }, { transaction: t });
      await tx.update({ status: "processing", meta: { ...(tx.meta || {}), approvedBy: adminId, approvedAt: new Date() } }, { transaction: t });

      return { withdrawalId: wd.id, txId: tx.transactionId, status: "processing" };
    });
  }

  async rejectWithdrawal(source, withdrawalId, data, adminId) {
    const reason = data?.reason ?? "Rejected by admin";
    return await sequelize.transaction(async (t) => {
      const wd = await Withdrawal.findByPk(withdrawalId, { transaction: t, lock: t.LOCK.UPDATE, include: [{ model: Transaction, as: "transaction" }] });
      if (!wd) throw new NotFoundError("Withdrawal not found");

      const tx = wd.transaction;
      if (!tx || tx.kind !== "withdrawal" || tx.source !== source) throw new BadRequestError("Withdrawal source mismatch");
      if (tx.status === "completed") throw new BadRequestError("Cannot reject completed withdrawal");

      const amount = Number(tx.amount ?? 0);
      if (!Number.isFinite(amount) || amount <= 0) throw new ValidationError("Invalid withdrawal amount");

      // UNLOCK funds back to available (if your request flow locked them)
      if (source === "funding") {
        // If funding withdrawals lock from UserBalance.locked, you can reverse it here.
        // If you only had single balance column, just credit back.
        const ub = await UserBalance.findOne({ where: { userId: tx.userId }, transaction: t, lock: t.LOCK.UPDATE });
        if (!ub) throw new ConflictError("User balance row missing");

        // If you have `locked` column:
        if (Object.prototype.hasOwnProperty.call(ub.dataValues, "locked")) {
          const locked = Number(ub.locked ?? 0);
          const available = Number(ub.balance ?? 0);
          await ub.update({ locked: Math.max(locked - amount, 0), balance: available + amount }, { transaction: t });
        } else {
          const available = Number(ub.balance ?? 0);
          await ub.update({ balance: available + amount }, { transaction: t });
        }
      } else {
        // wallet withdrawal unlock
        let walletId = wd.walletId;
        let wallet = walletId
          ? await Wallet.findByPk(walletId, { transaction: t, lock: t.LOCK.UPDATE })
          : await Wallet.findOne({ where: { userId: tx.userId, asset: wd.asset, network: wd.network }, transaction: t, lock: t.LOCK.UPDATE });

        if (!wallet) throw new ConflictError("User wallet not found for this withdrawal");

        const wb = await WalletBalance.findOne({ where: { walletId: wallet.id }, transaction: t, lock: t.LOCK.UPDATE });
        if (!wb) throw new ConflictError("Wallet balance row missing");

        const available = Number(wb.available ?? 0);
        const locked = Number(wb.locked ?? 0);
        await wb.update({ locked: Math.max(locked - amount, 0), available: available + amount }, { transaction: t });
      }

      await wd.update({ reviewStatus: "rejected", rejectedBy: adminId, rejectedAt: new Date(), reviewNote: reason }, { transaction: t });
      await tx.update({ status: "failed", meta: { ...(tx.meta || {}), rejectedBy: adminId, rejectedAt: new Date(), reason } }, { transaction: t });

      return { withdrawalId: wd.id, txId: tx.transactionId, status: "failed" };
    });
  }
}

module.exports = new adminWithdrawalsService();
