"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.p2pJobs = void 0;
exports.handleP2PTradeTimeouts = handleP2PTradeTimeouts;
exports.archiveOldP2PTrades = archiveOldP2PTrades;
exports.updateP2PReputationScores = updateP2PReputationScores;
const db_1 = require("@b/db");
const sequelize_1 = require("sequelize");
const utils_1 = require("@b/api/finance/wallet/utils");
const notifications_1 = require("@b/api/(ext)/p2p/utils/notifications");
const json_parser_1 = require("@b/api/(ext)/p2p/utils/json-parser");
const console_1 = require("@b/utils/console");
const wallet_1 = require("@b/services/wallet");
/**
 * P2P Trade Timeout Handler
 * This job runs periodically to handle expired trades
 */
async function handleP2PTradeTimeouts(ctx) {
    var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
    try {
        (_a = ctx === null || ctx === void 0 ? void 0 : ctx.step) === null || _a === void 0 ? void 0 : _a.call(ctx, "Starting P2P trade timeout handler");
        // Get default payment window setting (in minutes, default 30) as fallback
        const { CacheManager } = await Promise.resolve().then(() => __importStar(require("@b/utils/cache")));
        const cacheManager = CacheManager.getInstance();
        const defaultPaymentWindowMinutes = await cacheManager.getSetting("p2pDefaultPaymentWindow") || 30;
        // Find all trades that might have expired (we'll check each one individually)
        // We check trades older than 10 minutes (minimum reasonable timeout)
        const potentiallyExpiredCutoff = new Date();
        potentiallyExpiredCutoff.setMinutes(potentiallyExpiredCutoff.getMinutes() - 10);
        const potentiallyExpiredTrades = await db_1.models.p2pTrade.findAll({
            where: {
                status: {
                    [sequelize_1.Op.in]: ["PENDING", "PAYMENT_SENT"],
                },
                createdAt: {
                    [sequelize_1.Op.lt]: potentiallyExpiredCutoff,
                },
            },
            include: [
                {
                    model: db_1.models.p2pOffer,
                    as: "offer",
                    attributes: ["id", "currency", "walletType", "userId", "tradeSettings"],
                },
            ],
        });
        // Filter to actually expired trades based on offer-specific timeout
        const expiredTrades = potentiallyExpiredTrades.filter((trade) => {
            var _a, _b;
            const offerTimeout = ((_b = (_a = trade.offer) === null || _a === void 0 ? void 0 : _a.tradeSettings) === null || _b === void 0 ? void 0 : _b.autoCancel) || defaultPaymentWindowMinutes;
            const tradeAge = new Date().getTime() - new Date(trade.createdAt).getTime();
            const isExpired = tradeAge > (offerTimeout * 60 * 1000);
            return isExpired;
        });
        if (expiredTrades.length > 0) {
            (_b = ctx === null || ctx === void 0 ? void 0 : ctx.step) === null || _b === void 0 ? void 0 : _b.call(ctx, `Processing ${expiredTrades.length} expired trades`);
            console_1.logger.info("P2P", `Processing ${expiredTrades.length} expired trades`);
        }
        for (const trade of expiredTrades) {
            (_c = ctx === null || ctx === void 0 ? void 0 : ctx.step) === null || _c === void 0 ? void 0 : _c.call(ctx, `Processing expired trade ${trade.id}`);
            // Validate trade ID is a valid UUID before processing
            const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
            if (!uuidRegex.test(trade.id)) {
                console_1.logger.warn("P2P", `Invalid trade ID detected: ${trade.id}, deleting invalid trade`);
                try {
                    await db_1.models.p2pTrade.destroy({ where: { id: trade.id }, force: true });
                    console_1.logger.info("P2P", `Deleted invalid trade ${trade.id}`);
                }
                catch (deleteError) {
                    console_1.logger.error("P2P", `Failed to delete invalid trade ${trade.id}`, deleteError);
                }
                continue;
            }
            const transaction = await db_1.sequelize.transaction();
            try {
                // Lock the trade
                const lockedTrade = await db_1.models.p2pTrade.findByPk(trade.id, {
                    lock: true,
                    transaction,
                });
                // Double-check status hasn't changed and trade is still expired
                const offerTimeout = ((_e = (_d = trade.offer) === null || _d === void 0 ? void 0 : _d.tradeSettings) === null || _e === void 0 ? void 0 : _e.autoCancel) || defaultPaymentWindowMinutes;
                const tradeAge = new Date().getTime() - new Date(lockedTrade.createdAt).getTime();
                const isStillExpired = tradeAge > (offerTimeout * 60 * 1000);
                if (!lockedTrade ||
                    !["PENDING", "PAYMENT_SENT"].includes(lockedTrade.status) ||
                    !isStillExpired) {
                    await transaction.rollback();
                    continue;
                }
                // If funds were locked (seller's funds), release them
                // This applies to ALL wallet types including FIAT
                if ((lockedTrade.status === "PENDING" || lockedTrade.status === "PAYMENT_SENT") && trade.offer) {
                    try {
                        const sellerWallet = await (0, utils_1.getWalletSafe)(lockedTrade.sellerId, trade.offer.walletType, trade.offer.currency || lockedTrade.currency);
                        if (sellerWallet) {
                            // CRITICAL: Calculate safe unlock amount to prevent negative inOrder
                            const safeUnlockAmount = Math.min(trade.amount, sellerWallet.inOrder);
                            if (safeUnlockAmount > 0) {
                                // Use wallet service for atomic, audited release from hold
                                // Use stable idempotency key for proper retry detection
                                const idempotencyKey = `p2p_timeout_release_${trade.id}`;
                                await wallet_1.walletService.release({
                                    idempotencyKey,
                                    userId: lockedTrade.sellerId,
                                    walletId: sellerWallet.id,
                                    walletType: trade.offer.walletType,
                                    currency: trade.offer.currency || lockedTrade.currency,
                                    amount: safeUnlockAmount,
                                    operationType: "P2P_TRADE_EXPIRED",
                                    description: `Release ${safeUnlockAmount} ${trade.offer.currency || lockedTrade.currency} - P2P trade expired`,
                                    metadata: {
                                        tradeId: trade.id,
                                        offerId: trade.offerId,
                                        expiredAt: new Date().toISOString(),
                                        reason: 'timeout',
                                    },
                                    transaction,
                                });
                                console_1.logger.info("P2P", `Released ${safeUnlockAmount} ${trade.offer.currency || lockedTrade.currency} (${trade.offer.walletType}) for seller ${lockedTrade.sellerId}`);
                                console_1.logger.debug("P2P", `Trade unlock details: amount=${trade.amount}, prevInOrder=${sellerWallet.inOrder}`);
                                // Log warning if amounts don't match
                                if (safeUnlockAmount < trade.amount) {
                                    console_1.logger.warn("P2P", `Partial unlock - inOrder was less than trade amount: tradeId=${trade.id}, amount=${trade.amount}, available=${sellerWallet.inOrder}, unlocked=${safeUnlockAmount}`);
                                }
                            }
                            else {
                                console_1.logger.warn("P2P", `No funds to unlock - inOrder is already 0: tradeId=${trade.id}, amount=${trade.amount}, currentInOrder=${sellerWallet.inOrder}`);
                            }
                        }
                    }
                    catch (walletError) {
                        console_1.logger.error("P2P", `Failed to release wallet funds for trade ${trade.id}`, walletError);
                        // Continue with expiration even if fund release fails
                    }
                }
                // Update trade status - ensure timeline is an array (might be string from DB)
                let timeline = lockedTrade.timeline || [];
                if (typeof timeline === "string") {
                    try {
                        timeline = JSON.parse(timeline);
                    }
                    catch (_l) {
                        timeline = [];
                    }
                }
                if (!Array.isArray(timeline)) {
                    timeline = [];
                }
                timeline.push({
                    event: "TRADE_EXPIRED",
                    message: "Trade expired due to timeout",
                    userId: null, // System-generated event
                    createdAt: new Date().toISOString(),
                });
                await lockedTrade.update({
                    status: "EXPIRED",
                    timeline,
                    expiredAt: new Date(),
                }, { transaction });
                // If offer was associated, restore the amount
                // CRITICAL: Validate against original total to prevent over-restoration
                if (trade.offerId) {
                    const offer = await db_1.models.p2pOffer.findByPk(trade.offerId, {
                        lock: true,
                        transaction,
                    });
                    if (offer && offer.status === "ACTIVE") {
                        // Parse amountConfig with robust parser
                        const amountConfig = (0, json_parser_1.parseAmountConfig)(offer.amountConfig);
                        // Calculate safe restoration amount
                        const originalTotal = (_f = amountConfig.originalTotal) !== null && _f !== void 0 ? _f : (amountConfig.total + trade.amount);
                        const proposedTotal = amountConfig.total + trade.amount;
                        const safeTotal = Math.min(proposedTotal, originalTotal);
                        if (safeTotal > amountConfig.total) {
                            await offer.update({
                                amountConfig: {
                                    ...amountConfig,
                                    total: safeTotal,
                                    originalTotal,
                                },
                            }, { transaction });
                            console_1.logger.debug("P2P", `Restored offer amount: offerId=${offer.id}, tradeAmount=${trade.amount}, prevTotal=${amountConfig.total}, newTotal=${safeTotal}`);
                        }
                        else {
                            console_1.logger.debug("P2P", `Skipped restoration - at or above limit: offerId=${offer.id}, currentTotal=${amountConfig.total}, max=${originalTotal}`);
                        }
                    }
                }
                // Log activity for both buyer and seller (non-critical, don't fail expiration if this fails)
                try {
                    await db_1.models.p2pActivityLog.create({
                        userId: trade.sellerId,
                        type: "TRADE_EXPIRED",
                        action: "EXPIRED",
                        relatedEntity: "TRADE",
                        relatedEntityId: trade.id,
                        details: JSON.stringify({
                            previousStatus: lockedTrade.status,
                            amount: trade.amount,
                            currency: (_g = trade.offer) === null || _g === void 0 ? void 0 : _g.currency,
                            buyerId: trade.buyerId,
                            sellerId: trade.sellerId,
                            systemGenerated: true,
                        }),
                    }, { transaction });
                }
                catch (activityLogError) {
                    console_1.logger.warn("P2P", `Failed to create activity log for trade ${trade.id}, continuing with expiration`, activityLogError);
                }
                await transaction.commit();
                // Send notifications (non-blocking)
                (0, notifications_1.notifyTradeEvent)(trade.id, "TRADE_EXPIRED", {
                    buyerId: trade.buyerId,
                    sellerId: trade.sellerId,
                    amount: trade.amount,
                    currency: trade.offer.currency,
                }, ctx).catch((err) => console_1.logger.error("P2P", "Failed to notify trade event", err));
                (_h = ctx === null || ctx === void 0 ? void 0 : ctx.step) === null || _h === void 0 ? void 0 : _h.call(ctx, `Successfully expired trade ${trade.id}`);
                console_1.logger.info("P2P", `Successfully expired trade ${trade.id}`);
            }
            catch (error) {
                await transaction.rollback();
                console_1.logger.error("P2P", `Failed to expire trade ${trade.id}`, error);
            }
        }
        // Handle offers that need to expire
        await handleExpiredOffers(ctx);
        (_j = ctx === null || ctx === void 0 ? void 0 : ctx.success) === null || _j === void 0 ? void 0 : _j.call(ctx, "P2P trade timeout handler completed successfully");
    }
    catch (error) {
        (_k = ctx === null || ctx === void 0 ? void 0 : ctx.fail) === null || _k === void 0 ? void 0 : _k.call(ctx, error.message || "Trade timeout handler error");
        console_1.logger.error("P2P", "Trade timeout handler error", error);
    }
}
/**
 * Handle expired offers
 */
async function handleExpiredOffers(ctx) {
    var _a, _b, _c, _d, _e, _f;
    try {
        (_a = ctx === null || ctx === void 0 ? void 0 : ctx.step) === null || _a === void 0 ? void 0 : _a.call(ctx, "Checking for expired offers");
        // Find offers that should expire (e.g., older than 30 days with no activity)
        const OFFER_EXPIRY_DAYS = 30;
        const expiryDate = new Date();
        expiryDate.setDate(expiryDate.getDate() - OFFER_EXPIRY_DAYS);
        const expiredOffers = await db_1.models.p2pOffer.findAll({
            where: {
                status: "ACTIVE",
                updatedAt: {
                    [sequelize_1.Op.lt]: expiryDate,
                },
                [sequelize_1.Op.or]: [
                    (0, sequelize_1.literal)(`JSON_EXTRACT(\`amountConfig\`, '$.total') = 0`),
                    (0, sequelize_1.literal)(`JSON_EXTRACT(\`amountConfig\`, '$.total') IS NULL`),
                    (0, sequelize_1.literal)(`CAST(JSON_EXTRACT(\`amountConfig\`, '$.total') AS DECIMAL(36,18)) <= 0`),
                ],
            },
        });
        if (expiredOffers.length > 0) {
            (_b = ctx === null || ctx === void 0 ? void 0 : ctx.step) === null || _b === void 0 ? void 0 : _b.call(ctx, `Processing ${expiredOffers.length} expired offers`);
            console_1.logger.info("P2P", `Processing ${expiredOffers.length} expired offers`);
        }
        for (const offer of expiredOffers) {
            (_c = ctx === null || ctx === void 0 ? void 0 : ctx.step) === null || _c === void 0 ? void 0 : _c.call(ctx, `Processing expired offer ${offer.id}`);
            try {
                await offer.update({
                    status: "EXPIRED",
                    adminNotes: `Auto-expired due to inactivity and zero balance at ${new Date().toISOString()}`,
                });
                // Log activity for offer owner
                await db_1.models.p2pActivityLog.create({
                    userId: offer.userId,
                    type: "OFFER_EXPIRED",
                    action: "EXPIRED",
                    relatedEntity: "OFFER",
                    relatedEntityId: offer.id,
                    details: JSON.stringify({
                        reason: "inactivity_and_zero_balance",
                        lastUpdated: offer.updatedAt,
                        systemGenerated: true,
                    }),
                });
                // Notify user
                const { notifyOfferEvent } = await Promise.resolve().then(() => __importStar(require("@b/api/(ext)/p2p/utils/notifications")));
                notifyOfferEvent(offer.id, "OFFER_EXPIRED", {
                    reason: "Inactivity and zero balance",
                }, ctx).catch((err) => console_1.logger.error("P2P", "Failed to notify offer event", err));
                (_d = ctx === null || ctx === void 0 ? void 0 : ctx.step) === null || _d === void 0 ? void 0 : _d.call(ctx, `Expired offer ${offer.id}`);
                console_1.logger.info("P2P", `Expired offer ${offer.id}`);
            }
            catch (error) {
                console_1.logger.error("P2P", `Failed to expire offer ${offer.id}`, error);
            }
        }
        (_e = ctx === null || ctx === void 0 ? void 0 : ctx.success) === null || _e === void 0 ? void 0 : _e.call(ctx, "Expired offers handled successfully");
    }
    catch (error) {
        (_f = ctx === null || ctx === void 0 ? void 0 : ctx.fail) === null || _f === void 0 ? void 0 : _f.call(ctx, error.message || "Offer expiry handler error");
        console_1.logger.error("P2P", "Offer expiry handler error", error);
    }
}
/**
 * Clean up old completed trades (archive)
 */
async function archiveOldP2PTrades() {
    try {
        // Archive trades older than 90 days
        const ARCHIVE_DAYS = 90;
        const archiveDate = new Date();
        archiveDate.setDate(archiveDate.getDate() - ARCHIVE_DAYS);
        const tradesToArchive = await db_1.models.p2pTrade.findAll({
            where: {
                status: {
                    [sequelize_1.Op.in]: ["COMPLETED", "CANCELLED", "EXPIRED"],
                },
                updatedAt: {
                    [sequelize_1.Op.lt]: archiveDate,
                },
                archived: {
                    [sequelize_1.Op.or]: [false, null],
                },
            },
            limit: 100, // Process in batches
        });
        if (tradesToArchive.length > 0) {
            console_1.logger.info("P2P", `Archiving ${tradesToArchive.length} trades`);
        }
        for (const trade of tradesToArchive) {
            try {
                // Move sensitive data to archive table or mark as archived
                await trade.update({
                    archived: true,
                    archivedAt: new Date(),
                });
            }
            catch (error) {
                console_1.logger.error("P2P", `Failed to archive trade ${trade.id}`, error);
            }
        }
    }
    catch (error) {
        console_1.logger.error("P2P", "Trade archival error", error);
    }
}
/**
 * Calculate and update user reputation scores
 */
async function updateP2PReputationScores() {
    try {
        // Get all users with P2P activity in the last 30 days
        const activeUsers = await db_1.models.p2pTrade.findAll({
            attributes: [
                [(0, sequelize_1.fn)("DISTINCT", (0, sequelize_1.col)("buyerId")), "userId"],
            ],
            where: {
                createdAt: {
                    [sequelize_1.Op.gte]: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
                },
            },
            raw: true,
        });
        const sellerIds = await db_1.models.p2pTrade.findAll({
            attributes: [
                [(0, sequelize_1.fn)("DISTINCT", (0, sequelize_1.col)("sellerId")), "userId"],
            ],
            where: {
                createdAt: {
                    [sequelize_1.Op.gte]: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
                },
            },
            raw: true,
        });
        const allUserIds = [
            ...new Set([
                ...activeUsers.map((u) => u.userId),
                ...sellerIds.map((s) => s.userId),
            ]),
        ];
        if (allUserIds.length > 0) {
            console_1.logger.info("P2P", `Updating reputation for ${allUserIds.length} users`);
        }
        for (const userId of allUserIds) {
            try {
                // Calculate user stats
                const completedTrades = await db_1.models.p2pTrade.count({
                    where: {
                        [sequelize_1.Op.or]: [{ buyerId: userId }, { sellerId: userId }],
                        status: "COMPLETED",
                    },
                });
                const totalTrades = await db_1.models.p2pTrade.count({
                    where: {
                        [sequelize_1.Op.or]: [{ buyerId: userId }, { sellerId: userId }],
                        status: {
                            [sequelize_1.Op.ne]: "PENDING",
                        },
                    },
                });
                const disputedTrades = await db_1.models.p2pDispute.count({
                    where: {
                        [sequelize_1.Op.or]: [{ reportedById: userId }, { againstId: userId }],
                        status: "RESOLVED",
                    },
                });
                const avgRating = await db_1.models.p2pReview.findOne({
                    attributes: [
                        [(0, sequelize_1.fn)("AVG", (0, sequelize_1.col)("rating")), "avgRating"],
                    ],
                    where: {
                        reviewedUserId: userId,
                    },
                    raw: true,
                });
                // Calculate reputation score (0-100)
                let reputationScore = 50; // Base score
                if (totalTrades > 0) {
                    const completionRate = completedTrades / totalTrades;
                    reputationScore += completionRate * 30; // Up to 30 points for completion rate
                }
                if (avgRating && avgRating.avgRating) {
                    reputationScore += (avgRating.avgRating / 5) * 20; // Up to 20 points for ratings
                }
                // Deduct for disputes
                reputationScore -= Math.min(disputedTrades * 5, 20); // Max 20 point deduction
                // Ensure score is between 0 and 100
                reputationScore = Math.max(0, Math.min(100, Math.round(reputationScore)));
                // Check for milestones
                if (completedTrades === 10 || completedTrades === 50 || completedTrades === 100) {
                    const { notifyReputationEvent } = await Promise.resolve().then(() => __importStar(require("@b/api/(ext)/p2p/utils/notifications")));
                    notifyReputationEvent(userId, "REPUTATION_MILESTONE", {
                        milestone: completedTrades,
                        reputationScore,
                    }).catch((err) => console_1.logger.error("P2P", "Failed to notify reputation event", err));
                }
            }
            catch (error) {
                console_1.logger.error("P2P", `Failed to update reputation for user ${userId}`, error);
            }
        }
    }
    catch (error) {
        console_1.logger.error("P2P", "Reputation update error", error);
    }
}
// Export for cron job registration
exports.p2pJobs = {
    handleTradeTimeouts: {
        name: "p2p-trade-timeout",
        schedule: "*/5 * * * *", // Every 5 minutes
        handler: handleP2PTradeTimeouts,
    },
    archiveTrades: {
        name: "p2p-archive-trades",
        schedule: "0 2 * * *", // Daily at 2 AM
        handler: archiveOldP2PTrades,
    },
    updateReputation: {
        name: "p2p-update-reputation",
        schedule: "0 * * * *", // Every hour
        handler: updateP2PReputationScores,
    },
};
