"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubscriptionService = void 0;
const types_js_1 = require("../../domain/subscriptions/types.js");
const PaymentPlanModel_js_1 = require("../../infrastructure/database/models/PaymentPlanModel.js");
const PaymentTransactionModel_js_1 = require("../../infrastructure/database/models/PaymentTransactionModel.js");
const SubscriptionModel_js_1 = require("../../infrastructure/database/models/SubscriptionModel.js");
const UserModel_js_1 = require("../../infrastructure/database/models/UserModel.js");
const Amount_js_1 = require("../../infrastructure/payments/Amount.js");
const AdminEvents_js_1 = require("../../infrastructure/events/AdminEvents.js");
const paymentWindowMs = 15 * 60 * 1000;
const dailyPaymentRequestLimit = 5;
class SubscriptionService {
    config;
    walletService;
    balanceService;
    logger;
    constructor(config, walletService, balanceService, logger) {
        this.config = config;
        this.walletService = walletService;
        this.balanceService = balanceService;
        this.logger = logger;
    }
    isEnabled() {
        return Boolean(this.config.mongoUri && this.config.paymentEncryptionSecret && this.walletService && this.balanceService);
    }
    async seedPlans() {
        if (!this.isEnabled())
            return;
        await Promise.all(types_js_1.PAYMENT_PLANS.map((plan) => PaymentPlanModel_js_1.PaymentPlanModel.updateOne({ code: plan.code }, { $set: { ...plan, active: true } }, { upsert: true })));
        await SubscriptionModel_js_1.SubscriptionModel.syncIndexes();
        await PaymentTransactionModel_js_1.PaymentTransactionModel.syncIndexes();
    }
    async upsertUserFromContext(ctx) {
        const from = ctx.from;
        if (!from)
            return undefined;
        const telegramId = String(from.id);
        if (!this.isEnabled())
            return telegramId;
        await UserModel_js_1.UserModel.updateOne({ telegramId }, {
            $set: {
                username: from.username,
                firstName: from.first_name,
                lastName: from.last_name
            }
        }, { upsert: true });
        return telegramId;
    }
    async checkActiveSubscription(telegramId, platformCode) {
        if (!this.isEnabled())
            return false;
        const subscription = await SubscriptionModel_js_1.SubscriptionModel.findOne({ telegramId, platformCode, status: "active" });
        if (!subscription)
            return false;
        if (subscription.isLifetime)
            return true;
        if (!subscription.subscriptionEnd)
            return false;
        if (subscription.subscriptionEnd.getTime() > Date.now())
            return true;
        await SubscriptionModel_js_1.SubscriptionModel.updateOne({ _id: subscription._id }, { $set: { status: "expired" } });
        return false;
    }
    async getDailyRequestUsage(telegramId, platformCode) {
        if (!this.isEnabled())
            return undefined;
        const subscription = await SubscriptionModel_js_1.SubscriptionModel.findOne({ telegramId, platformCode, status: "active" });
        if (!subscription)
            return undefined;
        const plan = (0, types_js_1.getPaymentPlan)(subscription.selectedPlan);
        const limit = plan?.dailyRequestLimit ?? 0;
        if (limit <= 0)
            return undefined;
        const resetDue = this.isDailyResetDue(subscription.dailyRequestsResetAt);
        const made = resetDue ? 0 : subscription.dailyRequestsMade;
        const resetAt = resetDue ? this.getNextMidnight() : subscription.dailyRequestsResetAt ?? this.getNextMidnight();
        return { made, limit, remaining: Math.max(0, limit - made), resetAt };
    }
    async checkSignalRequestLimit(telegramId, platformCode) {
        if (!this.isEnabled()) {
            return { allowed: true, made: 0, limit: 0, remaining: 0, resetAt: this.getNextMidnight() };
        }
        const subscription = await SubscriptionModel_js_1.SubscriptionModel.findOne({ telegramId, platformCode, status: "active" });
        if (!subscription) {
            return { allowed: true, made: 0, limit: 0, remaining: 0, resetAt: this.getNextMidnight() };
        }
        const plan = (0, types_js_1.getPaymentPlan)(subscription.selectedPlan);
        const limit = plan?.dailyRequestLimit ?? 0;
        if (limit <= 0) {
            return { allowed: true, made: 0, limit: 0, remaining: 0, resetAt: this.getNextMidnight() };
        }
        const resetDue = this.isDailyResetDue(subscription.dailyRequestsResetAt);
        const currentMade = resetDue ? 0 : subscription.dailyRequestsMade;
        const resetAt = resetDue ? this.getNextMidnight() : subscription.dailyRequestsResetAt ?? this.getNextMidnight();
        if (currentMade >= limit) {
            return { allowed: false, made: currentMade, limit, remaining: 0, resetAt };
        }
        return { allowed: true, made: currentMade, limit, remaining: limit - currentMade, resetAt };
    }
    async recordSignalRequest(telegramId, platformCode) {
        if (!this.isEnabled()) {
            return { allowed: true, made: 0, limit: 0, remaining: 0, resetAt: this.getNextMidnight() };
        }
        const subscription = await SubscriptionModel_js_1.SubscriptionModel.findOne({ telegramId, platformCode, status: "active" });
        if (!subscription) {
            return { allowed: true, made: 0, limit: 0, remaining: 0, resetAt: this.getNextMidnight() };
        }
        const plan = (0, types_js_1.getPaymentPlan)(subscription.selectedPlan);
        const limit = plan?.dailyRequestLimit ?? 0;
        if (limit <= 0) {
            return { allowed: true, made: 0, limit: 0, remaining: 0, resetAt: this.getNextMidnight() };
        }
        const resetDue = this.isDailyResetDue(subscription.dailyRequestsResetAt);
        const currentMade = resetDue ? 0 : subscription.dailyRequestsMade;
        const resetAt = this.getNextMidnight();
        if (currentMade >= limit) {
            return { allowed: false, made: currentMade, limit, remaining: 0, resetAt };
        }
        const made = currentMade + 1;
        await SubscriptionModel_js_1.SubscriptionModel.updateOne({ _id: subscription._id }, { $set: { dailyRequestsMade: made, dailyRequestsResetAt: resetAt } });
        return { allowed: true, made, limit, remaining: limit - made, resetAt };
    }
    isDailyResetDue(resetAt) {
        if (!resetAt)
            return true;
        return new Date() >= resetAt;
    }
    getNextMidnight() {
        const d = new Date();
        d.setHours(0, 0, 0, 0);
        d.setDate(d.getDate() + 1);
        return d;
    }
    async getSubscriptionSummary(telegramId, platformCode) {
        if (!this.isEnabled())
            return "Subscriptions are not configured yet.";
        if (platformCode) {
            const subscription = await SubscriptionModel_js_1.SubscriptionModel.findOne({ telegramId, platformCode }).sort({ updatedAt: -1 });
            if (!subscription || subscription.status !== "active") {
                return `You do not have an active subscription for this platform. Use /subscribe to choose a plan.`;
            }
            const plan = (0, types_js_1.getPaymentPlan)(subscription.selectedPlan);
            const planName = plan?.name ?? subscription.selectedPlan;
            const usage = await this.getDailyRequestUsage(telegramId, platformCode);
            const usageLine = usage ? `Requests today: ${usage.made}/${usage.limit} used (${usage.remaining} left)` : undefined;
            const resetLine = usage ? `Resets at: ${usage.resetAt.toISOString()}` : undefined;
            if (subscription.isLifetime) {
                return [
                    `Your ${planName} subscription is active.`,
                    `Started: ${subscription.subscriptionStart.toISOString()}`,
                    usageLine,
                    resetLine
                ]
                    .filter(Boolean)
                    .join("\n");
            }
            return [
                `Your ${planName} subscription is active.`,
                `Started: ${subscription.subscriptionStart.toISOString()}`,
                `Ends: ${subscription.subscriptionEnd?.toISOString() ?? "unknown"}`,
                usageLine,
                resetLine
            ]
                .filter(Boolean)
                .join("\n");
        }
        const subscriptions = await SubscriptionModel_js_1.SubscriptionModel.find({ telegramId, status: "active" }).sort({ updatedAt: -1 });
        if (subscriptions.length === 0) {
            return "You do not have any active subscriptions. Use /subscribe to choose a plan.";
        }
        const lines = [];
        for (const sub of subscriptions) {
            const plan = (0, types_js_1.getPaymentPlan)(sub.selectedPlan);
            const planName = plan?.name ?? sub.selectedPlan;
            const platform = plan?.platformCode ?? sub.platformCode ?? "unknown";
            const end = sub.isLifetime ? "Lifetime" : sub.subscriptionEnd?.toISOString() ?? "unknown";
            const usage = await this.getDailyRequestUsage(telegramId, platform);
            const usagePart = usage ? ` — ${usage.made}/${usage.limit} used (${usage.remaining} left)` : "";
            lines.push(`• ${platform}: ${planName} — ${end}${usagePart}`);
        }
        return lines.join("\n");
    }
    async createOrGetPaymentRequest(input) {
        if (!this.isEnabled() || !this.walletService || !this.balanceService) {
            throw new Error("Subscription payments are not configured.");
        }
        if (!types_js_1.PAYMENT_NETWORKS.includes(input.network)) {
            throw new Error("Unsupported payment network.");
        }
        const plan = (0, types_js_1.getPaymentPlan)(input.planCode);
        if (!plan)
            throw new Error("Unknown subscription plan.");
        this.assertNetworkConfigured(input.network);
        const existing = await PaymentTransactionModel_js_1.PaymentTransactionModel.findOne({
            telegramId: input.telegramId,
            platformCode: plan.platformCode,
            status: "pending",
            expiresAt: { $gt: new Date() }
        }).sort({ createdAt: -1 });
        if (existing) {
            return { transaction: existing, reused: true, decimals: await this.balanceService.getDecimals(existing.network) };
        }
        const todayStart = new Date();
        todayStart.setHours(0, 0, 0, 0);
        const todayEnd = new Date(todayStart);
        todayEnd.setDate(todayEnd.getDate() + 1);
        const createdToday = await PaymentTransactionModel_js_1.PaymentTransactionModel.countDocuments({
            telegramId: input.telegramId,
            createdAt: { $gte: todayStart, $lt: todayEnd }
        });
        if (createdToday >= dailyPaymentRequestLimit) {
            throw new Error("You can only create 5 subscription payment requests per day.");
        }
        const wallet = await this.walletService.getOrCreateWallet(input.telegramId, input.network);
        const startingBalance = await this.balanceService.getUsdtBalance(input.network, wallet.walletAddress);
        const decimals = await this.balanceService.getDecimals(input.network);
        const expectedAmount = (0, Amount_js_1.parseTokenUnits)(plan.amountUsdt, decimals);
        const transaction = await PaymentTransactionModel_js_1.PaymentTransactionModel.create({
            telegramId: input.telegramId,
            username: input.username,
            platformCode: plan.platformCode,
            selectedPlan: input.planCode,
            network: input.network,
            walletAddress: wallet.walletAddress,
            encryptedPrivateKey: wallet.encryptedPrivateKey,
            keyReference: wallet.keyReference,
            expectedAmount,
            paidAmount: "0",
            startingBalance,
            status: "pending",
            isLifetime: plan.isLifetime,
            expiresAt: new Date(Date.now() + paymentWindowMs)
        });
        this.logger.info("Created subscription payment request.", {
            telegramId: input.telegramId,
            plan: input.planCode,
            network: input.network,
            walletAddress: wallet.walletAddress
        });
        try {
            AdminEvents_js_1.adminEvents.emitPaymentPending({
                id: transaction._id.toString(),
                telegramId: transaction.telegramId,
                username: transaction.username,
                selectedPlan: transaction.selectedPlan,
                network: transaction.network,
                walletAddress: transaction.walletAddress,
                expectedAmount: transaction.expectedAmount,
                paidAmount: transaction.paidAmount,
                status: transaction.status,
                createdAt: transaction.createdAt.toISOString()
            });
        }
        catch (error) {
            this.logger.error("Failed to emit payment pending event.", {
                paymentId: transaction._id.toString(),
                ...serializeError(error)
            });
        }
        return { transaction, reused: false, decimals };
    }
    async activateSubscription(transaction, paidAmount) {
        const plan = (0, types_js_1.getPaymentPlan)(transaction.selectedPlan);
        if (!plan)
            throw new Error("Unknown subscription plan.");
        const now = new Date();
        const subscriptionEnd = plan.isLifetime ? undefined : new Date(now.getTime() + (plan.durationDays ?? 0) * 24 * 60 * 60 * 1000);
        await PaymentTransactionModel_js_1.PaymentTransactionModel.updateOne({ _id: transaction._id, status: "pending" }, {
            $set: {
                status: "confirmed",
                paidAmount,
                confirmedAt: now,
                subscriptionStart: now,
                subscriptionEnd,
                isLifetime: plan.isLifetime
            }
        });
        await SubscriptionModel_js_1.SubscriptionModel.updateOne({ telegramId: transaction.telegramId, platformCode: plan.platformCode }, {
            $set: {
                telegramId: transaction.telegramId,
                username: transaction.username,
                platformCode: plan.platformCode,
                selectedPlan: transaction.selectedPlan,
                status: "active",
                subscriptionStart: now,
                subscriptionEnd,
                isLifetime: plan.isLifetime,
                lastPaymentId: transaction._id,
                dailyRequestsMade: 0,
                dailyRequestsResetAt: this.getNextMidnight()
            }
        }, { upsert: true });
        this.logger.info("Activated subscription.", {
            telegramId: transaction.telegramId,
            plan: transaction.selectedPlan,
            paidAmount
        });
    }
    async extendSubscription(telegramId, platformCode, days) {
        const now = new Date();
        const existing = await SubscriptionModel_js_1.SubscriptionModel.findOne({ telegramId, platformCode });
        const startFrom = existing?.subscriptionEnd && existing.subscriptionEnd > now ? existing.subscriptionEnd : now;
        await SubscriptionModel_js_1.SubscriptionModel.updateOne({ telegramId, platformCode }, {
            $set: {
                telegramId,
                platformCode,
                selectedPlan: "admin-extension",
                status: "active",
                subscriptionStart: existing?.subscriptionStart ?? now,
                subscriptionEnd: new Date(startFrom.getTime() + days * 24 * 60 * 60 * 1000),
                isLifetime: false
            }
        }, { upsert: true });
    }
    async grantLifetime(telegramId, platformCode) {
        await SubscriptionModel_js_1.SubscriptionModel.updateOne({ telegramId, platformCode }, {
            $set: {
                telegramId,
                platformCode,
                selectedPlan: "lifetime",
                status: "active",
                subscriptionStart: new Date(),
                isLifetime: true
            },
            $unset: { subscriptionEnd: "" }
        }, { upsert: true });
    }
    async revokeSubscription(telegramId, platformCode) {
        await SubscriptionModel_js_1.SubscriptionModel.updateOne({ telegramId, platformCode }, { $set: { status: "revoked", subscriptionEnd: new Date() } });
    }
    async listSubscriptions(limit = 20) {
        const subscriptions = await SubscriptionModel_js_1.SubscriptionModel.find().sort({ updatedAt: -1 }).limit(limit);
        if (subscriptions.length === 0)
            return "No subscriptions found.";
        return subscriptions
            .map((sub) => `${sub.telegramId} (${sub.platformCode}): ${sub.status} ${sub.selectedPlan}${sub.isLifetime ? " lifetime" : ` until ${sub.subscriptionEnd?.toISOString() ?? "unknown"}`}`)
            .join("\n");
    }
    async listPayments(limit = 20) {
        const payments = await PaymentTransactionModel_js_1.PaymentTransactionModel.find().sort({ createdAt: -1 }).limit(limit);
        if (payments.length === 0)
            return "No payments found.";
        return payments
            .map((payment) => `${payment.telegramId} (${payment.platformCode ?? "unknown"}): ${payment.status} ${payment.selectedPlan} ${payment.network} paid=${payment.paidAmount} expected=${payment.expectedAmount}`)
            .join("\n");
    }
    async getUserSubscription(telegramId, platformCode) {
        if (platformCode) {
            const subscription = await SubscriptionModel_js_1.SubscriptionModel.findOne({ telegramId, platformCode });
            if (!subscription)
                return `No subscription found for ${telegramId} on ${platformCode}.`;
            return JSON.stringify({
                telegramId: subscription.telegramId,
                platformCode: subscription.platformCode,
                status: subscription.status,
                selectedPlan: subscription.selectedPlan,
                subscriptionStart: subscription.subscriptionStart,
                subscriptionEnd: subscription.subscriptionEnd,
                isLifetime: subscription.isLifetime
            }, null, 2);
        }
        const subscriptions = await SubscriptionModel_js_1.SubscriptionModel.find({ telegramId }).sort({ updatedAt: -1 });
        if (subscriptions.length === 0)
            return `No subscriptions found for ${telegramId}.`;
        return JSON.stringify(subscriptions.map((sub) => ({
            telegramId: sub.telegramId,
            platformCode: sub.platformCode,
            status: sub.status,
            selectedPlan: sub.selectedPlan,
            subscriptionStart: sub.subscriptionStart,
            subscriptionEnd: sub.subscriptionEnd,
            isLifetime: sub.isLifetime
        })), null, 2);
    }
    paymentRequestMessage(transaction, decimals, reused) {
        const plan = (0, types_js_1.getPaymentPlan)(transaction.selectedPlan);
        const network = types_js_1.NETWORK_LABELS[transaction.network];
        const secondsRemaining = Math.max(0, Math.ceil((transaction.expiresAt.getTime() - Date.now()) / 1000));
        const minutes = Math.floor(secondsRemaining / 60);
        const seconds = secondsRemaining % 60;
        return [
            reused ? "You already have an active payment request. Send USDT to the same address before it expires." : "Subscription payment request created.",
            "",
            `Plan: ${plan?.name ?? transaction.selectedPlan}`,
            plan?.dailyRequestLimit ? `Daily request limit: ${plan.dailyRequestLimit}` : "",
            `Amount: ${(0, Amount_js_1.formatTokenUnits)(transaction.expectedAmount, decimals)} USDT`,
            `Network: ${network.name}`,
            `Token standard: ${network.standard}`,
            `Deposit address: ${transaction.walletAddress}`,
            "Scan the QR code or copy the address above.",
            `Expires in: ${minutes}m ${seconds}s`,
            "",
            `Send only ${network.standard} to this address on ${network.name}.`,
            "Do not send native coins, seed phrases, or private keys.",
            "Your subscription activates automatically after the USDT balance increase is detected."
        ].join("\n");
    }
    assertNetworkConfigured(network) {
        if (network === "tron") {
            if (!this.config.paymentNetworks.tron)
                throw new Error("Tron payments are not configured.");
            return;
        }
        if (!this.config.paymentNetworks[network])
            throw new Error(`${types_js_1.NETWORK_LABELS[network].name} payments are not configured.`);
    }
}
exports.SubscriptionService = SubscriptionService;
const serializeError = (error) => {
    if (error instanceof Error)
        return { name: error.name, message: error.message, stack: error.stack };
    return { error };
};
