"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VaultGasService = void 0;
const ethers_1 = require("ethers");
const tronweb_1 = require("tronweb");
const BalanceService_js_1 = require("./BalanceService.js");
const SweepVaultModel_js_1 = require("../database/models/SweepVaultModel.js");
const VaultSwapService_js_1 = require("./VaultSwapService.js");
const MnemonicWallet_js_1 = require("./MnemonicWallet.js");
class VaultGasService {
    config;
    cryptoBox;
    logger;
    balanceService;
    swapService;
    constructor(config, cryptoBox, logger) {
        this.config = config;
        this.cryptoBox = cryptoBox;
        this.logger = logger;
        this.balanceService = new BalanceService_js_1.BalanceService(config, logger);
        this.swapService = new VaultSwapService_js_1.VaultSwapService(config, logger);
    }
    async ensureVaultGasForSweep(network, paymentWalletAddress, sweepGasEstimate, vaultId) {
        const vault = await this.resolveVault(vaultId);
        if (!vault) {
            return { funded: false, error: vaultId ? "Selected sweep vault not found." : "No active sweep vault found." };
        }
        const seedPhrase = this.cryptoBox.decrypt(vault.encryptedSeedPhrase);
        const vaultWallet = (0, MnemonicWallet_js_1.deriveVaultWallet)(seedPhrase, network);
        if (network === "tron") {
            return this.ensureTronGas(vaultWallet.privateKey, vault.tronAddress, paymentWalletAddress, sweepGasEstimate);
        }
        return this.ensureEvmGas(network, vaultWallet.privateKey, vault.evmAddress, paymentWalletAddress, sweepGasEstimate);
    }
    async resolveVault(vaultId) {
        if (vaultId) {
            try {
                const vault = await SweepVaultModel_js_1.SweepVaultModel.findById(vaultId).lean();
                if (vault)
                    return vault;
            }
            catch {
                // Invalid vault id format; fall through to active vault lookup.
            }
        }
        return SweepVaultModel_js_1.SweepVaultModel.findOne({ isActive: true }).lean();
    }
    async ensureEvmGas(network, privateKey, vaultAddress, paymentWalletAddress, sweepGasEstimate) {
        const networkConfig = this.config.paymentNetworks[network];
        if (!networkConfig) {
            return { funded: false, error: `${network} payments are not configured.` };
        }
        const nativeDecimals = 18;
        const provider = new ethers_1.ethers.JsonRpcProvider(networkConfig.rpcUrl);
        const signer = new ethers_1.ethers.Wallet(privateKey, provider);
        if (signer.address.toLowerCase() !== vaultAddress.toLowerCase()) {
            return { funded: false, error: "Derived vault address does not match stored vault address." };
        }
        const feeData = await provider.getFeeData();
        const gasPrice = feeData.maxFeePerGas ?? feeData.gasPrice ?? 0n;
        if (gasPrice === 0n) {
            return { funded: false, error: "Could not determine gas price." };
        }
        const topUpGasLimit = 21000n;
        const swapGasLimit = networkConfig.swapRouterType === "uniswap-v3" ? 350000n : 250000n;
        const oneDollarOfNative = this.usdToNative(1, networkConfig.nativePriceUsd, nativeDecimals);
        // Total native the vault must still hold after sending the top-up:
        //  - gas for the future swap it may need to do
        //  - $1 worth of native reserve
        const reserveNeeded = swapGasLimit * gasPrice + oneDollarOfNative;
        // Native the vault must send to the payment wallet for the sweep.
        const topUpNeeded = sweepGasEstimate;
        // Total required in vault right now.
        const totalRequired = topUpNeeded + topUpGasLimit * gasPrice + reserveNeeded;
        const nativeBalance = await provider.getBalance(vaultAddress);
        const usdtBalance = BigInt(await this.balanceService.getUsdtBalance(network, vaultAddress));
        this.logger?.info(`Vault gas check for ${network}.`, {
            vaultAddress,
            paymentWalletAddress,
            nativeBalance: nativeBalance.toString(),
            usdtBalance: usdtBalance.toString(),
            totalRequired: totalRequired.toString(),
            topUpNeeded: topUpNeeded.toString(),
            reserveNeeded: reserveNeeded.toString()
        });
        let swapResult;
        if (nativeBalance < totalRequired) {
            // Not enough native. Try to swap USDT to native first.
            const swapCallCost = swapGasLimit * gasPrice;
            if (nativeBalance < swapCallCost) {
                return {
                    funded: false,
                    error: `Vault native balance too low to even broadcast a USDT->native swap. Need ${swapCallCost.toString()}, have ${nativeBalance.toString()}.`
                };
            }
            // Amount of native we are short.
            const nativeShortfall = totalRequired - nativeBalance + oneDollarOfNative; // add a small buffer
            const usdtNeeded = this.nativeToUsdt(nativeShortfall, networkConfig.nativePriceUsd, await this.getDecimals(network), nativeDecimals);
            if (usdtBalance < usdtNeeded) {
                return {
                    funded: false,
                    error: `Vault USDT balance insufficient to swap for needed native token. Need ${usdtNeeded.toString()} USDT, have ${usdtBalance.toString()}.`
                };
            }
            try {
                swapResult = await this.swapService.swapUsdtToNative(network, privateKey, vaultAddress, usdtNeeded);
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return { funded: false, error: `USDT->native swap failed: ${message}` };
            }
            // Re-read native balance after swap.
            const newNativeBalance = await provider.getBalance(vaultAddress);
            if (newNativeBalance < totalRequired) {
                return {
                    funded: false,
                    error: `After swap, vault native balance still insufficient. Need ${totalRequired.toString()}, have ${newNativeBalance.toString()}.`
                };
            }
        }
        // Send top-up to payment wallet.
        try {
            const tx = await signer.sendTransaction({
                to: paymentWalletAddress,
                value: topUpNeeded,
                gasLimit: topUpGasLimit,
                gasPrice
            });
            await tx.wait();
            return {
                funded: true,
                feeTxHash: tx.hash,
                swapped: !!swapResult,
                swapTxHash: swapResult?.txHash,
                swapAmountIn: swapResult?.amountIn,
                swapAmountOut: swapResult?.amountOut
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { funded: false, error: `Failed to send gas top-up: ${message}` };
        }
    }
    async ensureTronGas(privateKey, vaultAddress, paymentWalletAddress, sweepGasEstimate) {
        const networkConfig = this.config.paymentNetworks.tron;
        if (!networkConfig) {
            return { funded: false, error: "Tron payments are not configured." };
        }
        const tronWeb = new tronweb_1.TronWeb({
            fullHost: networkConfig.fullHost,
            headers: this.tronHeaders(networkConfig.apiKey),
            privateKey: privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey
        });
        const derivedAddress = tronWeb.defaultAddress.base58;
        if (!derivedAddress || derivedAddress.toLowerCase() !== vaultAddress.toLowerCase()) {
            return { funded: false, error: "Derived Tron vault address does not match stored vault address." };
        }
        // Tron energy/bandwidth is hard to estimate in TRX precisely. Use rough TRX budgets.
        const nativeDecimals = 6;
        const topUpNeeded = 30n * 1000000n; // 30 TRX for the payment wallet sweep
        const swapCallTrx = 50n * 1000000n; // 50 TRX to broadcast a SunSwap swap
        const oneDollarOfTrx = this.usdToNative(1, networkConfig.nativePriceUsd, nativeDecimals); // ~6.6 TRX
        const reserveNeeded = swapCallTrx + oneDollarOfTrx;
        const totalRequired = topUpNeeded + reserveNeeded;
        const nativeBalance = BigInt(await tronWeb.trx.getBalance(vaultAddress));
        const usdtBalance = BigInt(await this.balanceService.getUsdtBalance("tron", vaultAddress));
        this.logger?.info(`Vault gas check for tron.`, {
            vaultAddress,
            paymentWalletAddress,
            nativeBalance: nativeBalance.toString(),
            usdtBalance: usdtBalance.toString(),
            totalRequired: totalRequired.toString()
        });
        let swapResult;
        if (nativeBalance < totalRequired) {
            if (nativeBalance < swapCallTrx) {
                return {
                    funded: false,
                    error: `Vault TRX balance too low to broadcast a USDT->TRX swap. Need ${swapCallTrx.toString()}, have ${nativeBalance.toString()}.`
                };
            }
            const nativeShortfall = totalRequired - nativeBalance + oneDollarOfTrx;
            const usdtNeeded = this.nativeToUsdt(nativeShortfall, networkConfig.nativePriceUsd, await this.getDecimals("tron"), nativeDecimals);
            if (usdtBalance < usdtNeeded) {
                return {
                    funded: false,
                    error: `Vault USDT balance insufficient to swap for needed TRX. Need ${usdtNeeded.toString()} USDT, have ${usdtBalance.toString()}.`
                };
            }
            try {
                swapResult = await this.swapService.swapUsdtToNative("tron", privateKey, vaultAddress, usdtNeeded);
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return { funded: false, error: `USDT->TRX swap failed: ${message}` };
            }
            const newNativeBalance = BigInt(await tronWeb.trx.getBalance(vaultAddress));
            if (newNativeBalance < totalRequired) {
                return {
                    funded: false,
                    error: `After swap, vault TRX balance still insufficient. Need ${totalRequired.toString()}, have ${newNativeBalance.toString()}.`
                };
            }
        }
        try {
            const txResult = await tronWeb.trx.sendTransaction(paymentWalletAddress, Number(topUpNeeded));
            const txHash = txResult.txid;
            if (!txHash) {
                return { funded: false, error: "Tron top-up did not return a transaction hash." };
            }
            return {
                funded: true,
                feeTxHash: txHash,
                swapped: !!swapResult,
                swapTxHash: swapResult?.txHash,
                swapAmountIn: swapResult?.amountIn,
                swapAmountOut: swapResult?.amountOut
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { funded: false, error: `Failed to send TRX top-up: ${message}` };
        }
    }
    async getDecimals(network) {
        return this.balanceService.getDecimals(network);
    }
    usdToNative(usd, nativePriceUsd, nativeDecimals) {
        const nativeAmount = usd / nativePriceUsd;
        return BigInt(Math.round(nativeAmount * 10 ** nativeDecimals));
    }
    nativeToUsdt(nativeAmount, nativePriceUsd, usdtDecimals, nativeDecimals) {
        const nativeFloat = Number(nativeAmount) / 10 ** nativeDecimals;
        const usd = nativeFloat * nativePriceUsd;
        return BigInt(Math.round(usd * 10 ** usdtDecimals));
    }
    tronHeaders(apiKey) {
        return apiKey ? { "TRON-PRO-API-KEY": apiKey } : undefined;
    }
}
exports.VaultGasService = VaultGasService;
