"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PaymentMonitor = void 0;
const types_js_1 = require("../../domain/subscriptions/types.js");
const PaymentTransactionModel_js_1 = require("../../infrastructure/database/models/PaymentTransactionModel.js");
const Amount_js_1 = require("../../infrastructure/payments/Amount.js");
const AdminEvents_js_1 = require("../../infrastructure/events/AdminEvents.js");
const menu_js_1 = require("../../app/menu.js");
class PaymentMonitor {
    bot;
    subscriptionService;
    balanceService;
    adminTelegramIds;
    pollIntervalSeconds;
    requiredConfirmations;
    logger;
    interval;
    running = false;
    constructor(bot, subscriptionService, balanceService, adminTelegramIds, pollIntervalSeconds, requiredConfirmations, logger) {
        this.bot = bot;
        this.subscriptionService = subscriptionService;
        this.balanceService = balanceService;
        this.adminTelegramIds = adminTelegramIds;
        this.pollIntervalSeconds = pollIntervalSeconds;
        this.requiredConfirmations = requiredConfirmations;
        this.logger = logger;
    }
    start() {
        if (this.interval)
            return;
        this.interval = setInterval(() => void this.tick(), this.pollIntervalSeconds * 1000);
        void this.tick();
        this.logger.info("Subscription payment monitor started.", { pollIntervalSeconds: this.pollIntervalSeconds });
    }
    stop() {
        if (this.interval)
            clearInterval(this.interval);
        this.interval = undefined;
    }
    async tick() {
        if (this.running)
            return;
        this.running = true;
        try {
            await this.checkPendingPayments();
            await this.checkLatePayments();
            await this.expireSubscriptions();
            await this.sendExpiryReminders();
        }
        catch (error) {
            this.logger.error("Payment monitor tick failed.", serializeError(error));
        }
        finally {
            this.running = false;
        }
    }
    async checkPendingPayments() {
        const pending = await PaymentTransactionModel_js_1.PaymentTransactionModel.find({ status: "pending" }).limit(50);
        const now = new Date();
        for (const payment of pending) {
            try {
                const currentBalance = await this.balanceService.getUsdtBalance(payment.network, payment.walletAddress);
                const paidDiff = (0, Amount_js_1.positiveDifference)(currentBalance, payment.startingBalance);
                // Unconfirmed transactions always expose currentBalance as "0". The actual balance is only
                // persisted once the payment is confirmed.
                await PaymentTransactionModel_js_1.PaymentTransactionModel.updateOne({ _id: payment._id }, { $set: { currentBalance: "0" } });
                if (paidDiff >= BigInt(payment.expectedAmount)) {
                    const confirmationChecks = (payment.confirmationChecks ?? 0) + 1;
                    await PaymentTransactionModel_js_1.PaymentTransactionModel.updateOne({ _id: payment._id, status: "pending" }, {
                        $set: {
                            paidAmount: paidDiff.toString(),
                            firstSufficientBalanceAt: payment.firstSufficientBalanceAt ?? now,
                            confirmationChecks
                        }
                    });
                    if (confirmationChecks < Math.max(1, this.requiredConfirmations)) {
                        continue;
                    }
                    await PaymentTransactionModel_js_1.PaymentTransactionModel.updateOne({ _id: payment._id }, { $set: { currentBalance } });
                    await this.subscriptionService.activateSubscription(payment, paidDiff.toString());
                    this.emitPaymentEvent(payment._id.toString(), "confirmed");
                    const decimals = await this.balanceService.getDecimals(payment.network);
                    await this.bot.telegram.sendMessage(payment.telegramId, subscriptionActivatedMessage((0, Amount_js_1.formatTokenUnits)(paidDiff, decimals), types_js_1.NETWORK_LABELS[payment.network].name), (0, menu_js_1.mainMenu)());
                    continue;
                }
                if (payment.expiresAt <= now) {
                    await PaymentTransactionModel_js_1.PaymentTransactionModel.updateOne({ _id: payment._id, status: "pending" }, { $set: { status: "expired", paidAmount: paidDiff.toString(), currentBalance: "0", expiredNotifiedAt: now } });
                    this.emitPaymentEvent(payment._id.toString(), "expired");
                    await this.bot.telegram.sendMessage(payment.telegramId, "Your subscription payment request expired before enough USDT was received. Please create a new request with /subscribe.");
                }
            }
            catch (error) {
                await PaymentTransactionModel_js_1.PaymentTransactionModel.updateOne({ _id: payment._id }, { $set: { failureReason: error instanceof Error ? error.message : String(error) } });
                this.logger.error("Failed to check pending payment.", { paymentId: payment._id.toString(), ...serializeError(error) });
            }
        }
    }
    async checkLatePayments() {
        const recentCutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
        const expired = await PaymentTransactionModel_js_1.PaymentTransactionModel.find({
            status: "expired",
            latePaymentNotifiedAt: { $exists: false },
            createdAt: { $gte: recentCutoff }
        }).limit(30);
        for (const payment of expired) {
            try {
                const currentBalance = await this.balanceService.getUsdtBalance(payment.network, payment.walletAddress);
                const paidDiff = (0, Amount_js_1.positiveDifference)(currentBalance, payment.startingBalance);
                if (paidDiff < BigInt(payment.expectedAmount))
                    continue;
                await PaymentTransactionModel_js_1.PaymentTransactionModel.updateOne({ _id: payment._id, status: "expired" }, { $set: { status: "late_payment", paidAmount: paidDiff.toString(), currentBalance: "0", latePaymentNotifiedAt: new Date() } });
                await this.notifyAdmins([
                    "Late subscription payment detected.",
                    `Telegram ID: ${payment.telegramId}`,
                    `Plan: ${payment.selectedPlan}`,
                    `Network: ${types_js_1.NETWORK_LABELS[payment.network].name}`,
                    `Wallet: ${payment.walletAddress}`,
                    `Paid amount units: ${paidDiff.toString()}`
                ].join("\n"));
            }
            catch (error) {
                this.logger.error("Failed to check late payment.", { paymentId: payment._id.toString(), ...serializeError(error) });
            }
        }
    }
    async expireSubscriptions() {
        const { SubscriptionModel } = await import("../../infrastructure/database/models/SubscriptionModel.js");
        await SubscriptionModel.updateMany({ status: "active", isLifetime: false, subscriptionEnd: { $lte: new Date() } }, { $set: { status: "expired" } });
    }
    async sendExpiryReminders() {
        // Placeholder for reminder scheduling. The subscription status check is automatic;
        // a persisted reminder flag can be added later if repeated reminders become noisy.
    }
    async notifyAdmins(message) {
        await Promise.allSettled(this.adminTelegramIds.map((id) => this.bot.telegram.sendMessage(id, message)));
    }
    emitPaymentEvent(id, event) {
        void PaymentTransactionModel_js_1.PaymentTransactionModel.findById(id)
            .lean()
            .then((tx) => {
            if (!tx)
                return;
            const payload = {
                id: tx._id.toString(),
                telegramId: tx.telegramId,
                username: tx.username,
                selectedPlan: tx.selectedPlan,
                network: tx.network,
                walletAddress: tx.walletAddress,
                expectedAmount: tx.expectedAmount,
                paidAmount: tx.paidAmount,
                status: tx.status,
                createdAt: tx.createdAt.toISOString()
            };
            if (event === "confirmed") {
                AdminEvents_js_1.adminEvents.emitPaymentConfirmed(payload);
            }
            else {
                AdminEvents_js_1.adminEvents.emitPaymentExpired(payload);
            }
        })
            .catch((error) => {
            this.logger.error("Failed to emit payment event.", {
                paymentId: id,
                event,
                ...serializeError(error)
            });
        });
    }
}
exports.PaymentMonitor = PaymentMonitor;
const serializeError = (error) => {
    if (error instanceof Error)
        return { name: error.name, message: error.message, stack: error.stack };
    return { error };
};
const subscriptionActivatedMessage = (paidAmount, networkName) => [
    `Payment confirmed: ${paidAmount} USDT on ${networkName}.`,
    "Your premium subscription is now active.",
    "",
    "Quant Whisper surfaces live DeFi trading signals from on-chain and market data.",
    "",
    "How to use the bot:",
    "1. Select a market below.",
    "2. Choose your signal options and review the full trade setup.",
    "3. Use /payment_status or My Subscription to check your access anytime."
].join("\n");
