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

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

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

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

    return {
      source,
      count,
      page: Number(page),
      limit: Number(limit),
      deposits: rows.map(d => ({
        id: d.id,
        tx: d.transaction?.transactionId,
        userId: d.transaction?.userId,
        amount: Number(d.transaction?.amount ?? 0),
        asset: d.asset,
        network: d.network,
        methodId: d.methodId,
        status: d.transaction?.status,
        createdAt: d.created_at,
        proof: d.metadata?.proof
      })),
    };
  }

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

      const tx = deposit.transaction;
      if (!tx || tx.kind !== "deposit" || tx.source !== source) throw new BadRequestError("Deposit source mismatch");
      if (tx.status === "completed") throw new BadRequestError("Deposit already completed");
      if (tx.status === "failed") throw new BadRequestError("Deposit already failed");

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

      // CREDIT:
      // - funding deposits credit UserBalance.balance
      // - wallet deposits credit WalletBalance.available for that wallet
      if (source === "funding") {
        const ub = await UserBalance.findOne({ where: { userId: tx.userId }, transaction: t, lock: t.LOCK.UPDATE });
        if (!ub) throw new ConflictError("User balance row missing");
        const current = Number(ub.balance ?? 0);
        await ub.update({ balance: current + amount }, { transaction: t });
      } else {
        // wallet: require walletId on deposit OR deposit.asset+network find user's wallet
        let walletId = deposit.walletId;
        let wallet = walletId
          ? await Wallet.findByPk(walletId, { transaction: t, lock: t.LOCK.UPDATE })
          : await Wallet.findOne({ where: { userId: tx.userId, asset: deposit.asset, network: deposit.network }, transaction: t, lock: t.LOCK.UPDATE });

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

        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);
        await wb.update({ available: available + amount }, { transaction: t });
      }

      await tx.update({ status: "completed", meta: { ...(tx.meta || {}), confirmedBy: adminId, confirmedAt: new Date() } }, { transaction: t });

      return { depositId: deposit.id, txId: tx.transactionId, status: "completed" };
    });
  }

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

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

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

      return { depositId: deposit.id, txId: tx.transactionId, status: "failed" };
    });
  }
}

module.exports = new adminDepositsService();
