"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.metadata = void 0;
const db_1 = require("@b/db");
const error_1 = require("@b/utils/error");
const crypto_1 = __importDefault(require("crypto"));
const utils_1 = require("@b/api/(ext)/admin/ico/utils");
const notifications_1 = require("@b/utils/notifications");
const Middleware_1 = require("@b/handler/Middleware");
const sequelize_1 = require("sequelize");
const console_1 = require("@b/utils/console");
const wallet_1 = require("@b/services/wallet");
exports.metadata = {
    summary: "Create a New ICO Investment",
    description: "Creates a new ICO investment transaction for the authenticated user using icoTransaction only. The wallet type and currency are derived from the associated plan. It also deducts funds from the user's wallet, records the transaction, updates offering stats, and sends email and in‑app notifications to both investor and seller.",
    operationId: "createIcoInvestment",
    tags: ["ICO", "Investments"],
    requiresAuth: true,
    logModule: "ICO_INVESTMENT",
    logTitle: "Purchase ICO tokens",
    requestBody: {
        required: true,
        content: {
            "application/json": {
                schema: {
                    type: "object",
                    properties: {
                        offeringId: { type: "string", description: "ICO offering ID" },
                        amount: { type: "number", description: "Investment amount" },
                        walletAddress: {
                            type: "string",
                            description: "Wallet address where tokens will be sent",
                        },
                    },
                    required: ["offeringId", "amount", "walletAddress"],
                },
            },
        },
    },
    responses: {
        200: {
            description: "ICO investment transaction created successfully.",
            content: {
                "application/json": {
                    schema: {
                        type: "object",
                        properties: {
                            message: {
                                type: "string",
                                description: "Success message",
                            },
                            transactionId: {
                                type: "string",
                                description: "Transaction ID",
                            },
                            tokenAmount: {
                                type: "number",
                                description: "Number of tokens purchased",
                            },
                        },
                    },
                },
            },
        },
        400: { description: "Missing required fields or insufficient balance." },
        401: { description: "Unauthorized." },
        500: { description: "Internal Server Error." },
    },
};
// Helper function to validate wallet address based on blockchain
function validateWalletAddress(address, blockchain) {
    const validators = {
        ethereum: /^0x[a-fA-F0-9]{40}$/,
        bsc: /^0x[a-fA-F0-9]{40}$/,
        polygon: /^0x[a-fA-F0-9]{40}$/,
        bitcoin: /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/,
        solana: /^[1-9A-HJ-NP-Za-km-z]{32,44}$/,
    };
    const validator = validators[blockchain.toLowerCase()];
    return validator ? validator.test(address) : true;
}
// Get investment limits from settings
async function getInvestmentLimits() {
    const settings = await db_1.models.settings.findAll({
        where: {
            key: {
                [sequelize_1.Op.in]: ['icoMinInvestment', 'icoMaxInvestment', 'icoMaxPerUser']
            }
        }
    });
    const limits = settings.reduce((acc, setting) => {
        acc[setting.key] = parseFloat(setting.value) || 0;
        return acc;
    }, {});
    return {
        minInvestment: limits.icoMinInvestment || 10,
        maxInvestment: limits.icoMaxInvestment || 100000,
        maxPerUser: limits.icoMaxPerUser || 50000,
    };
}
exports.default = async (data) => {
    // Apply rate limiting
    await Middleware_1.rateLimiters.orderCreation(data);
    const { body, user, ctx } = data;
    if (!(user === null || user === void 0 ? void 0 : user.id)) {
        throw (0, error_1.createError)({ statusCode: 401, message: "Unauthorized" });
    }
    ctx === null || ctx === void 0 ? void 0 : ctx.step("Validating purchase request");
    const { offeringId, amount, walletAddress } = body;
    if (!offeringId || !amount || !walletAddress) {
        throw (0, error_1.createError)({ statusCode: 400, message: "Missing required fields" });
    }
    // Validate investment amount
    if (!Number.isFinite(amount) || amount <= 0) {
        throw (0, error_1.createError)({ statusCode: 400, message: "Invalid investment amount" });
    }
    // Start a transaction to ensure atomic operations
    const transaction = await db_1.sequelize.transaction();
    try {
        ctx === null || ctx === void 0 ? void 0 : ctx.step("Retrieving ICO offering details");
        // Retrieve the ICO offering with lock
        const offering = await db_1.models.icoTokenOffering.findByPk(offeringId, {
            include: [
                {
                    model: db_1.models.icoTokenDetail,
                    as: "tokenDetail",
                    required: true,
                },
            ],
            transaction,
            lock: transaction.LOCK.UPDATE,
        });
        if (!offering) {
            throw (0, error_1.createError)({ statusCode: 404, message: "Offering not found" });
        }
        ctx === null || ctx === void 0 ? void 0 : ctx.step("Validating offering status and eligibility");
        // Validate offering status
        if (offering.status !== 'ACTIVE') {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: `Offering is ${offering.status.toLowerCase()}. Only active offerings can receive investments.`
            });
        }
        // Check if offering has started and not ended
        const now = new Date();
        if (now < offering.startDate) {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: "Offering has not started yet"
            });
        }
        if (now > offering.endDate) {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: "Offering has ended"
            });
        }
        // Validate wallet address
        const blockchain = offering.tokenDetail.blockchain;
        if (!validateWalletAddress(walletAddress, blockchain)) {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: `Invalid ${blockchain} wallet address format`
            });
        }
        ctx === null || ctx === void 0 ? void 0 : ctx.step("Checking investment limits and user eligibility");
        // Get investment limits
        const limits = await getInvestmentLimits();
        // Validate investment amount against limits
        if (amount < limits.minInvestment) {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: `Minimum investment is ${limits.minInvestment} ${offering.purchaseWalletCurrency}`
            });
        }
        if (amount > limits.maxInvestment) {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: `Maximum investment is ${limits.maxInvestment} ${offering.purchaseWalletCurrency}`
            });
        }
        // Check user's total investment in this offering
        const existingInvestments = await db_1.models.icoTransaction.findAll({
            where: {
                userId: user.id,
                offeringId: offering.id,
                status: { [sequelize_1.Op.in]: ['PENDING', 'VERIFICATION', 'RELEASED'] }
            },
            transaction,
        });
        const totalUserInvestment = existingInvestments.reduce((sum, inv) => {
            return sum + (inv.amount * inv.price);
        }, 0);
        if (totalUserInvestment + amount > limits.maxPerUser) {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: `Maximum investment per user is ${limits.maxPerUser} ${offering.purchaseWalletCurrency}. You have already invested ${totalUserInvestment}.`
            });
        }
        ctx === null || ctx === void 0 ? void 0 : ctx.step("Finding active phase and calculating token amount");
        // Find active phase
        const activePhase = await db_1.models.icoTokenOfferingPhase.findOne({
            where: {
                offeringId: offering.id,
                remaining: { [sequelize_1.Op.gt]: 0 }
            },
            order: [['sequence', 'ASC']],
            transaction,
            lock: transaction.LOCK.UPDATE,
        });
        if (!activePhase) {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: "No tokens available for sale"
            });
        }
        // Use the active phase's token price
        const tokenPrice = activePhase.tokenPrice;
        // Get token details for decimals
        const ecosystemToken = await db_1.models.ecosystemToken.findOne({
            where: {
                currency: offering.symbol,
                chain: blockchain.toLowerCase()
            },
            transaction,
        });
        const decimals = (ecosystemToken === null || ecosystemToken === void 0 ? void 0 : ecosystemToken.decimals) || 18;
        // Calculate token amount with proper decimal handling
        const tokenAmount = (amount / tokenPrice) * Math.pow(10, decimals);
        const tokenAmountNormalized = amount / tokenPrice;
        // Check if phase has enough tokens
        if (tokenAmountNormalized > activePhase.remaining) {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: `Only ${activePhase.remaining} tokens remaining in current phase`
            });
        }
        // Check if total raised doesn't exceed target
        const totalRaised = await db_1.models.icoTransaction.sum('amount', {
            where: {
                offeringId: offering.id,
                status: { [sequelize_1.Op.in]: ['PENDING', 'VERIFICATION', 'RELEASED'] }
            },
            transaction,
        }) || 0;
        if (totalRaised + amount > offering.targetAmount) {
            const remainingCap = offering.targetAmount - totalRaised;
            throw (0, error_1.createError)({
                statusCode: 400,
                message: `Investment exceeds target amount. Only ${remainingCap} ${offering.purchaseWalletCurrency} remaining.`
            });
        }
        ctx === null || ctx === void 0 ? void 0 : ctx.step("Verifying wallet balance");
        // Lock and verify wallet balance
        const wallet = await db_1.models.wallet.findOne({
            where: {
                userId: user.id,
                type: offering.purchaseWalletType,
                currency: offering.purchaseWalletCurrency,
            },
            transaction,
            lock: transaction.LOCK.UPDATE,
        });
        if (!wallet) {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: `No ${offering.purchaseWalletType} wallet found for ${offering.purchaseWalletCurrency}. Please create a wallet first.`,
            });
        }
        // Check available balance (balance might need to exclude locked funds)
        const availableBalance = wallet.balance || 0;
        if (availableBalance < amount) {
            throw (0, error_1.createError)({
                statusCode: 400,
                message: `Insufficient wallet balance. Required: ${amount} ${offering.purchaseWalletCurrency}, Available: ${availableBalance} ${offering.purchaseWalletCurrency}`,
            });
        }
        ctx === null || ctx === void 0 ? void 0 : ctx.step("Deducting payment from wallet");
        // Use wallet service for atomic, audited debit (creates transaction automatically)
        // Use stable idempotency key for proper retry detection
        const idempotencyKey = `ico_invest_${offeringId}_${user.id}_${amount}`;
        const walletResult = await wallet_1.walletService.debit({
            idempotencyKey,
            userId: user.id,
            walletId: wallet.id,
            walletType: offering.purchaseWalletType,
            currency: offering.purchaseWalletCurrency,
            amount,
            operationType: "ICO_CONTRIBUTION",
            description: `ICO Investment in ${offering.name} - ${tokenAmountNormalized} tokens at ${tokenPrice} ${offering.purchaseWalletCurrency} each`,
            metadata: {
                offeringId: offering.id,
                offeringName: offering.name,
                phase: activePhase.name,
                tokenAmount: tokenAmountNormalized,
                tokenPrice,
                walletAddress,
            },
            transaction,
        });
        ctx === null || ctx === void 0 ? void 0 : ctx.step("Allocating tokens and creating ICO transaction record");
        // Generate a unique transaction ID
        const transactionId = crypto_1.default.randomBytes(16).toString("hex");
        // Create the icoTransaction record (linked to wallet transaction)
        const icoTransaction = await db_1.models.icoTransaction.create({
            userId: user.id,
            offeringId: offering.id,
            amount: tokenAmountNormalized,
            price: tokenPrice,
            status: "PENDING",
            transactionId,
            walletAddress,
            notes: JSON.stringify({
                phase: activePhase.name,
                decimals,
                rawTokenAmount: tokenAmount.toString(),
                investmentAmount: amount,
                currency: offering.purchaseWalletCurrency,
                walletTransactionId: walletResult.transactionId,
            }),
        }, { transaction });
        ctx === null || ctx === void 0 ? void 0 : ctx.step("Updating phase and offering statistics");
        // Update phase remaining tokens
        await activePhase.update({ remaining: activePhase.remaining - tokenAmountNormalized }, { transaction });
        // Update the offering's participant count
        const isNewParticipant = existingInvestments.length === 0;
        if (isNewParticipant) {
            await offering.update({ participants: offering.participants + 1 }, { transaction });
        }
        // Create audit log
        await db_1.models.icoAdminActivity.create({
            type: "INVESTMENT_CREATED",
            offeringId: offering.id,
            offeringName: offering.name,
            adminId: user.id,
            details: JSON.stringify({
                investor: user.email,
                amount,
                tokenAmount: tokenAmountNormalized,
                phase: activePhase.name,
                walletAddress,
            }),
        }, { transaction });
        // Commit the transaction after successful operations
        await transaction.commit();
        ctx === null || ctx === void 0 ? void 0 : ctx.step("Sending email and in-app notifications");
        // --- Send Email Notifications ---
        // Buyer (investor) email notification
        if (user.email) {
            await (0, utils_1.sendIcoBuyerEmail)(user.email, {
                INVESTOR_NAME: `${user.firstName} ${user.lastName}`,
                OFFERING_NAME: offering.name,
                AMOUNT_INVESTED: amount.toFixed(2),
                TOKEN_AMOUNT: tokenAmountNormalized.toFixed(4),
                TOKEN_PRICE: tokenPrice.toFixed(4),
                TRANSACTION_ID: transactionId,
            }, ctx);
        }
        // Seller (project owner) email notification
        const owner = await db_1.models.user.findByPk(offering.userId);
        if (owner && owner.email) {
            await (0, utils_1.sendIcoSellerEmail)(owner.email, {
                SELLER_NAME: `${owner.firstName} ${owner.lastName}`,
                OFFERING_NAME: offering.name,
                INVESTOR_NAME: `${user.firstName} ${user.lastName}`,
                AMOUNT_INVESTED: amount.toFixed(2),
                TOKEN_AMOUNT: tokenAmountNormalized.toFixed(4),
                TRANSACTION_ID: transactionId,
            }, ctx);
        }
        // --- Send In-App Notifications ---
        // Notify the investor (buyer)
        try {
            await (0, notifications_1.createNotification)({
                userId: user.id,
                relatedId: offering.id,
                type: "investment",
                title: "Investment Confirmed",
                message: `Your investment of ${amount} ${offering.purchaseWalletCurrency} in ${offering.name} has been confirmed.`,
                details: `You have purchased ${tokenAmountNormalized.toFixed(4)} tokens at ${tokenPrice} ${offering.purchaseWalletCurrency} per token. Transaction ID: ${transactionId}`,
                link: `/ico/dashboard?tab=transactions`,
                actions: [
                    {
                        label: "View Transaction",
                        link: `/ico/dashboard?tab=transactions`,
                        primary: true,
                    },
                ],
            }, ctx);
        }
        catch (notifErr) {
            console_1.logger.error("ICO_TRANSACTION", "Failed to create in-app notification for buyer", notifErr);
        }
        // Notify the seller (creator)
        try {
            await (0, notifications_1.createNotification)({
                userId: offering.userId,
                relatedId: offering.id,
                type: "system",
                title: "New Investment Received",
                message: `New investment of ${amount} ${offering.purchaseWalletCurrency} in ${offering.name}`,
                details: `Investor: ${user.firstName} ${user.lastName}\nAmount: ${amount} ${offering.purchaseWalletCurrency}\nTokens: ${tokenAmountNormalized.toFixed(4)}\nPhase: ${activePhase.name}`,
                link: `/ico/creator/token/${offering.id}?tab=transactions`,
                actions: [
                    {
                        label: "View Details",
                        link: `/ico/creator/token/${offering.id}?tab=transactions`,
                        primary: true,
                    },
                ],
            }, ctx);
        }
        catch (notifErr) {
            console_1.logger.error("ICO_TRANSACTION", "Failed to create in-app notification for seller", notifErr);
        }
        ctx === null || ctx === void 0 ? void 0 : ctx.success(`Purchased ${tokenAmountNormalized.toFixed(4)} tokens for ${amount} ${offering.purchaseWalletCurrency}`);
        // Return a successful response
        return {
            message: "ICO investment transaction created successfully.",
            transactionId,
            tokenAmount: tokenAmountNormalized,
        };
    }
    catch (err) {
        await transaction.rollback();
        ctx === null || ctx === void 0 ? void 0 : ctx.fail(err.message || "Failed to process ICO investment");
        throw err;
    }
};
