import { NAVISDKClient } from "navi-sdk";
import { Transaction } from "@mysten/sui/transactions";
import { flashloan, repayFlashLoan, SignAndSubmitTXB } from "navi-sdk/dist/libs/PTB";
import { CoinInfo, PoolConfig, Pool } from "navi-sdk/dist/types";
import { pool, wUSDC, nUSDC } from "navi-sdk/dist/address";
import dotenv from "dotenv";

dotenv.config();

/**
 * Configuration Zone
 *
 * NOTE:
 * - `wUSDC` (Wormhole USDC): USDC bridged to the Sui network via the Wormhole protocol.
 * - `nUSDC` (Native USDC): Native USDC issued directly on the Sui network.
 *
 * Choose the appropriate token based on your use case.
 */
const CONFIG: {
    mnemonic: string | undefined;
    networkType: string;
    numberOfAccounts: number;
    toBorrowCoin: CoinInfo; 
    amountToBorrow: number;
} = {
    mnemonic: process.env.mnemonic, // Wallet mnemonic
    networkType: "mainnet", // Network type
    numberOfAccounts: 1, // Number of accounts to initialize
    toBorrowCoin: nUSDC, // Coin to borrow (default: Native USDC)
    amountToBorrow: 1, // Borrow amount (in whole units, e.g., 1 Native USDC)
};
if (!CONFIG.mnemonic) {
    throw new Error("Mnemonic is not defined in environment variables");
}


/**
 * SECURITY WARNING:
 * Ensure that your `.env` file containing the mnemonic is included in `.gitignore`
 * to prevent accidental uploads to GitHub or other repositories.
 */

// Initialize NAVISDKClient
const client = new NAVISDKClient({
    mnemonic: CONFIG.mnemonic,
    networkType: CONFIG.networkType,
    numberOfAccounts: CONFIG.numberOfAccounts,
});

/**
 * Main function to execute the flashloan process
 */
async function executeFlashLoan() {
    const tx = new Transaction();
    const account = client.accounts[0];
    const sender = account.address;

    console.log("Sender Address:", sender);
    tx.setSender(sender);

    try {
        // Fetch the coin object for the account
        const sourceTokenObjAddress = await account.getCoins(CONFIG.toBorrowCoin);
        const coinObjects = sourceTokenObjAddress.data || [];
        if (coinObjects.length === 0) {
            throw new Error(`No coin objects found for ${CONFIG.toBorrowCoin.symbol}`);
        }
        const sourceTokenObj = tx.object(coinObjects[0].coinObjectId);

        // Retrieve loan pool configuration
        const loanPoolConfig: PoolConfig = pool[CONFIG.toBorrowCoin.symbol as keyof Pool];
        console.log("Loan Pool Configuration:", loanPoolConfig);

        // Calculate the amount to borrow in smallest units (wei-like units)
        const borrowAmountInWei = CONFIG.amountToBorrow * 10 ** CONFIG.toBorrowCoin.decimal;

        // Execute flashloan and get the borrowed balance
        const [borrowedBalance, receipt] = await flashloan(tx, loanPoolConfig, borrowAmountInWei);

        // Merge borrowed funds with the wallet balance
        const flashCoin = createCoinFromBalance(tx, borrowedBalance, loanPoolConfig.type);
        tx.mergeCoins(sourceTokenObj, [flashCoin]);

        // Prepare repayment balance
        const repayBalance = createCoinIntoBalance(tx, sourceTokenObj, loanPoolConfig.type);

        // Repay the flashloan
        const [remainingBalance] = await repayFlashLoan(tx, loanPoolConfig, receipt, repayBalance);

        // Transfer remaining funds back to the wallet
        const extraCoin = createCoinFromBalance(tx, remainingBalance, loanPoolConfig.type);
        tx.transferObjects([extraCoin], sender);

        // Sign and submit the transaction
        const result = await SignAndSubmitTXB(tx, account.client, account.keypair);
        console.log("Transaction Result:", result);
    } catch (error) {
        console.error("Error during flashloan execution:", error);
    }
}

/**
 * Helper function to create a coin from a balance
 */
function createCoinFromBalance(tx: Transaction, balance: any, type: string) {
    return tx.moveCall({
        target: "0x2::coin::from_balance",
        arguments: [balance],
        typeArguments: [type],
    });
}

/**
 * Helper function to convert a coin object into a balance
 */
function createCoinIntoBalance(tx: Transaction, coin: any, type: string) {
    return tx.moveCall({
        target: "0x2::coin::into_balance",
        arguments: [coin],
        typeArguments: [type],
    });
}

// Execute the script
(async () => {
    await executeFlashLoan();
})();
