
import { describe, it, expect } from 'vitest';
import { NAVISDKClient } from '../src/index';
import { NAVX, nUSDC, Sui, wUSDC } from '../src/address';
import { Transaction } from "@mysten/sui/transactions";
import { borrowCoin, depositCoin, withdrawCoin, repayDebt, stakeTovSuiPTB, updateOraclePTB, swapPTB, SignAndSubmitTXB, checkIfNAVIIntegrated } from '../src/libs/PTB';
import { Pool, PoolConfig, CoinInfo, OptionType } from "../src/types";
import { getConfig, pool, AddressMap, vSui } from "../src/address";
import { error } from 'console';
import dotenv from 'dotenv';
import { AccountManager } from '../src/libs/AccountManager';
import { migrateBorrowPTB, migrateSupplyPTB } from '../src/libs/PTB/migrate';
import { SuiClient } from '@mysten/sui/client';
import { haSui } from "../src/address";
import { generateRefId } from '../src/libs/Aggregator/utils';
dotenv.config();

const rpcUrl = process.env.RPC || '';
const mnemonic = process.env.MNEMONIC || '';
const privateKey = process.env.PRIVATE_KEY || '';
const apiKey = process.env.API_KEY || '';
async function dryRunTXB(txb: Transaction, client: SuiClient) {
    const dryRunTxBytes: Uint8Array = await txb.build({
        client: client
    });
    const dryRunResult = await client.dryRunTransactionBlock({ transactionBlock: dryRunTxBytes });
    return dryRunResult;
}

describe('NAVI SDK Client', async () => {

    const client = new NAVISDKClient({ networkType: rpcUrl, mnemonic: mnemonic });
    const account = client.accounts[0];
    it('should generate correct account', async () => {
        expect(client.accounts[0].getPublicKey()).toBe(client.getAllAccounts()[0].getPublicKey());
    });
    it('should generate correct account with private key in client', async () => {
        const client1 = new NAVISDKClient({ privateKeyList: [privateKey, privateKey], networkType: rpcUrl });
        expect(client1.accounts.length).toBe(2);
        expect(client1.accounts[0].getPublicKey()).toBe(client.accounts[0].getPublicKey());
    });

    it('should generate correct account with private key with AccountManager', async () => {
        const account = new AccountManager({ privateKey: privateKey });
        expect(account.getPublicKey()).toBe(client.getAllAccounts()[0].getPublicKey());
    });
    it('should get correct pool Info', async () => {
        const coinAddress = '0xa99b8952d4f7d947ea77fe0ecdcc9e5fc0bcab2841d6e2a5aa00c3044e5544b5::navx::NAVX';
        const res = await client.getPoolInfo(NAVX)
        expect(res.coin_type).toBe(coinAddress);

    });
    it('should get correct reserve Info', async () => {
        const poolObjectId = '0x2e13b2f1f714c0c5fa72264f147ef7632b48ec2501f810c07df3ccb59d6fdc81';
        const res = await client.getReserveDetail(NAVX)
        expect(res.data?.objectId).toBe(poolObjectId);

    });
    it('should get correct check user health factor', async () => {
        const userToCheck = '0x523c19fd6af645a12c6cb69bff0740b693aedd3f7613b1b20aa40d78f45204be';
        const res = await client.getHealthFactor(userToCheck)
        expect(res).toBeGreaterThan(1);
        expect(res).toBeLessThan(20);

    });
    it('should get correct check user dynamic health factor', async () => {
        const userToCheck = '0x523c19fd6af645a12c6cb69bff0740b693aedd3f7613b1b20aa40d78f45204be';
        const res = await client.getDynamicHealthFactor(userToCheck, Sui, 1e9, 0, true)
        const resNumber = Number(res);
        expect(resNumber).toBeGreaterThan(1);
        expect(resNumber).toBeLessThan(20);

    });
    it('should get correct return all accounts\' Navi Portfolio', async () => {

        const res = await client.getAllNaviPortfolios();
        const haSui: any = res.get('HaedalSui');

        expect(haSui.borrowBalance).toBe(0);
        expect(haSui.supplyBalance).toBe(0);

    });
    it('should get correct return all accounts\' wallet balance', async () => {

        const res = await client.getAllBalances();
        const sui: any = res;

        expect(typeof sui).toBe('object');

    });
    it('should get correct return available rewards for specific address', async () => {
        const address = '0xcb98748e6a6bb10d0250eeaef3aade81003b5ac25034c08a95e090768473144b';

        const res = await client.getAddressAvailableRewards(address);
        const reward = res['5'];

        expect(reward.asset_id).toBe('5');

    });
    it('should check if NAVI is integrated', async () => {
        const digest = "4JwmF4UFESM2b18wgNgGsfHA7MStTDAomLuQQnUaa6qr";
        const res = await checkIfNAVIIntegrated(digest, account.client);
        expect(res).toBe(true);
    });
    it('should check if NAVI is not integrated', async () => {
        const digest = "F94zASa3cudFffhetkW898MDt3ZT1qEmxvAUULp6BSbP";
        const res = await checkIfNAVIIntegrated(digest, account.client);
        expect(res).toBe(false);
    });
    it('should generate correct ref_id', async () => {
        const apiKey = '5aaaaa-test-4844-bf46-1d6dff248e7a';
        const res = 
              (apiKey);
        expect(res).toBe(5307806464);

    });
    it('should return same ref_id for same apiKey', async () => {
        const apiKey = '5aaaaa-test-4844-bf46-1d6dff248e7a';
        const res1 = generateRefId(apiKey);
        const res2 = generateRefId(apiKey);
        expect(res1).toBe(res2);
    });
    it('should return different ref_id for different apiKey', async () => {
        const apiKey1 = '5aaaaa-test-4844-bf46-1d6dff248e7a';
        const apiKey2 = '6bbbbb-test-4844-bf46-1d6dff248e7b';
        const res1 = generateRefId(apiKey1);
        const res2 = generateRefId(apiKey2);
        expect(res1).not.toBe(res2);
    });
    it('should generate ref_id as an integer and meet contract precision requirements', async () => {
        const apiKey = '5aaaaa-test-4844-bf46-1d6dff248e7a';
        const res = generateRefId(apiKey);
        expect(Number.isInteger(res)).toBe(true);
        expect(res).toBeGreaterThanOrEqual(0);
        expect(res).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER);
    });

});

describe('NAVI SDK Account Manager', async () => {
    const client = new NAVISDKClient({ networkType: rpcUrl, mnemonic: mnemonic });
    const account = client.accounts[0];

    it('should return correct public key', async () => {
        const publicKey = account.getPublicKey();
        expect(account.address).toBe(publicKey);
    });
    it('should return correct derivationPath', async () => {
        const path = account.getDerivationPath(0);
        expect(path).toBe(`m/44'/784'/0'/0'/0'`);
    });
    it('should return correct coin decimal', async () => {
        const path = await account.getCoinDecimal(Sui);
        expect(path).toEqual(9);
    });
    it('should success deposit Sui to NAVI protocol', async () => {
        let txb = new Transaction();
        txb.setSender(account.address);
        const poolConfig: PoolConfig = pool["Sui" as keyof Pool];

        const [toDeposit] = txb.splitCoins(txb.gas, [1e9]);
        await depositCoin(txb, poolConfig, toDeposit, 1e9);

        const result = await account.client.devInspectTransactionBlock({
            transactionBlock: txb,
            sender: account.address,
        })

        if (result.effects.status.status === "failure") {
            throw new Error(result.effects.status.error)
        }
        expect(result.effects.status.status).toEqual("success");

    });
    it('should success withdraw Sui to NAVI protocol', async () => {
        let txb = new Transaction();
        txb.setSender(account.address);
        const poolConfig: PoolConfig = pool["Sui" as keyof Pool];
        const [toDeposit] = txb.splitCoins(txb.gas, [1e9]);
        await depositCoin(txb, poolConfig, toDeposit, 1e9);
        await withdrawCoin(txb, poolConfig, 1e9);

        const result = await account.client.devInspectTransactionBlock({
            transactionBlock: txb,
            sender: account.address,
        })
        if (result.effects.status.status === "failure") {
            throw new Error(result.effects.status.error)
        }
        expect(result.effects.status.status).toEqual("success");

    });
    it('should success borrow Sui from NAVI protocol', async () => {
        let txb = new Transaction();
        txb.setSender(account.address);
        const poolConfig: PoolConfig = pool["Sui" as keyof Pool];
        const [toDeposit] = txb.splitCoins(txb.gas, [1e9]);
        await depositCoin(txb, poolConfig, toDeposit, 1e9);
        await borrowCoin(txb, poolConfig, 0.5e9);

        const result = await account.client.devInspectTransactionBlock({
            transactionBlock: txb,
            sender: account.address,
        })
        if (result.effects.status.status === "failure") {
            throw new Error(result.effects.status.error)
        }
        expect(result.effects.status.status).toEqual("success");

    });
    it('should success Repay Sui from NAVI protocol - random address might fail', async () => {
        let txb = new Transaction();
        txb.setSender('0x010ac247d4b9f8fcc9704b53b4aee5c4b00c3263b17bc39d3898ea802518bac9');
        const poolConfig: PoolConfig = pool["USDT" as keyof Pool];
        const [toRepay] = txb.splitCoins("0x7ae2714cb57cad133e62b83c58c59f4332a912256193203e86905571aa420d03", [0.1e6]);

        await repayDebt(txb, poolConfig, toRepay, 0.1e6);

        const result = await account.client.devInspectTransactionBlock({
            transactionBlock: txb,
            sender: '0x010ac247d4b9f8fcc9704b53b4aee5c4b00c3263b17bc39d3898ea802518bac9'
        })
        if (result.effects.status.status === "failure") {
            throw new Error(result.effects.status.error)
        }
        expect(result.effects.status.status).toEqual("success");

    });
    it('should success stake Sui to Volo Sui', async () => {
        let txb = new Transaction();

        const [toStake] = txb.splitCoins(txb.gas, [1e9]);

        await stakeTovSuiPTB(txb, toStake);

        const result = await account.client.devInspectTransactionBlock({
            transactionBlock: txb,
            sender: account.address
        })
        if (result.effects.status.status === "failure") {
            throw new Error(result.effects.status.error)
        }
        expect(result.effects.status.status).toEqual("success");

    });
    it('should success update oracle', async () => {
        let txb = new Transaction();
        txb.setSender(account.address);
        await updateOraclePTB(account.client, txb);

        const result = await account.client.devInspectTransactionBlock({
            transactionBlock: txb,
            sender: account.address,
        })
        if (result.effects.status.status === "failure") {
            throw new Error(result.effects.status.error)
        }
        expect(result.effects.status.status).toEqual("success");

    });

    it('should get correct NS rewards', async () => {
        const res = await client.getAddressAvailableRewards('0x33be2c133f87c268e48b527a7c62a509ad862b01e1eb4cf671ea5064218cfdc0');
        expect(res).toHaveProperty('13extra');
        expect(Number(res['13extra'].available)).toBeGreaterThan(0);
    });
    it('should get correct quote', async () => {
        const res = await client.getQuote(Sui.address, nUSDC.address, 1e9, '');
        console.log(res);
        expect(Number(res.amount_in)).toBeGreaterThan(0);
        expect(Number(res.amount_out)).toBeGreaterThan(0);
        expect(res.routes.length).toBeGreaterThan(0);
    });
    it('should build swap ptb from quote without fee', async () => {
        const txb = new Transaction();
        const from = "0x2::sui::SUI";
        const to = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC"
        const amount = 1e9;
        const fromCoin = txb.splitCoins(txb.gas, [amount]);

        swapPTB(account.address, txb, from, to, fromCoin, amount, 0, ""
        ).then(coinB => {
            txb.transferObjects([coinB], account.address);
            account.client.devInspectTransactionBlock({
                transactionBlock: txb,
                sender: account.address,
            }).then(result => {
                if (result.effects.status.status === "failure") {
                    throw new Error(result.effects.status.error)
                }
                expect(result.effects.status.status).toEqual("success");
            });
        // txb.build({
        //     client: account.client
        // }).then(dryRunTxBytes => {
        //     account.client.dryRunTransactionBlock({ transactionBlock: dryRunTxBytes }).then(res => {
        //         console.log(res);

            //     });
            // });
        });

    });

    it('should build swap ptb from quote with fee', async () => {
        const txb = new Transaction();
        const from = "0x2::sui::SUI";
        const to = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC"
        const amount = 1e9;
        const fromCoin = txb.splitCoins(txb.gas, [amount]);

        swapPTB(account.address, txb, from, to, fromCoin, amount, 0, "", {
            feeOption: {
                fee: 0.1, //10% of fee
                receiverAddress: account.address
            }
        }
        ).then(coinB => {
            txb.transferObjects([coinB], account.address);
            account.client.devInspectTransactionBlock({
                transactionBlock: txb,
                sender: account.address,
            }).then(result => {
                if (result.effects.status.status === "failure") {
                    throw new Error(result.effects.status.error)
                }
                expect(result.effects.status.status).toEqual("success");
            });
        });
    });
});


describe('test Migration', async () => {
    const client = new NAVISDKClient({ networkType: rpcUrl, mnemonic: mnemonic });
    const account = client.accounts[0];

    it('should migrate supply from USDC to Sui', async () => {
        const txb = new Transaction();
        txb.setSender(account.address);
        const amount = 1e6;
        await migrateSupplyPTB(txb, nUSDC, Sui, amount, account.address, { apiKey: apiKey });
        const dryRunResult = await dryRunTXB(txb, account.client);
        expect(dryRunResult.effects.status.status).toEqual("success");

    });
    it('should migrate supply from Sui to USDC', async () => {
        const txb = new Transaction();
        txb.setSender(account.address);
        const amount = 1e6;
        await migrateSupplyPTB(txb, Sui, nUSDC, amount, account.address, { apiKey: apiKey });
        const dryRunResult = await dryRunTXB(txb, account.client);
        expect(dryRunResult.effects.status.status).toEqual("success");

    });
    it('should migrate borrow from Sui to USDC', async () => {
        const txb = new Transaction();
        txb.setSender(account.address);
        const amount = 1e6;
        await migrateBorrowPTB(txb, Sui, nUSDC, amount, account.address, { apiKey: apiKey });
        const dryRunResult = await dryRunTXB(txb, account.client);
        expect(dryRunResult.effects.status.status).toEqual("success");

    });
    it('should migrate repay from USDC to Sui', async () => {
        const txb = new Transaction();
        txb.setSender(account.address);
        const amount = 1e6;
        await migrateBorrowPTB(txb, nUSDC, Sui, amount, account.address, { apiKey: apiKey });
        const dryRunResult = await dryRunTXB(txb, account.client);
        expect(dryRunResult.effects.status.status).toEqual("success");
    });

    it('should throw error if fromCoin and toCoin are the same', async () => {
        const txb = new Transaction();
        txb.setSender(account.address);
        const amount = 1e6;
        try {
            await migrateSupplyPTB(txb, nUSDC, nUSDC, amount, account.address, { apiKey: apiKey });
        } catch (error) {
            console.log("Caught error:", error.message);
            expect(error.message).toBe("fromCoin and toCoin cannot be the same");
        }
    });

    it('should throw error if amount is 0', async () => {
        const txb = new Transaction();
        txb.setSender(account.address);
        const amount = 0;
        try {
            await migrateSupplyPTB(txb, nUSDC, Sui, amount, account.address, { apiKey: apiKey });
        } catch (error) {
            console.log("Caught error:", error.message);
            expect(error.message).toBe("amount must be greater than 0");
        }
    });

    it('should able to migrate at big amount of flashloan', async () => {
        const txb = new Transaction();
        const sender = '0x03fbfd4e4ee3ca0ca58612a4919257270c035164db21d8c2cfc042ce93ee9065';
        txb.setSender(sender);
        const amount = 300000e6;
        await migrateSupplyPTB(txb, wUSDC, Sui, amount, sender, { apiKey: apiKey });
        const dryRunResult = await dryRunTXB(txb, account.client);
        expect(dryRunResult.effects.status.status).toEqual("success");
    });

    it('should throw error if unsupported coin', async () => {
        const txb = new Transaction();
        const sender = '0x03fbfd4e4ee3ca0ca58612a4919257270c035164db21d8c2cfc042ce93ee9065';
        txb.setSender(sender);
        const amount = 300000e9;
        try {
            await migrateSupplyPTB(txb, haSui, Sui, amount, account.address, { apiKey: apiKey });
        } catch (error) {
            console.log("Caught error:", error.message);
            expect(error.message).toBe("Unsupported coin");
        }
    });

    it('should work well without apikey', async () => {
        const txb = new Transaction();
        const sender = '0x03fbfd4e4ee3ca0ca58612a4919257270c035164db21d8c2cfc042ce93ee9065';
        txb.setSender(sender);
        const amount = 100e6;
        await migrateSupplyPTB(txb, wUSDC, Sui, amount, account.address, { apiKey: apiKey });
        const dryRunResult = await dryRunTXB(txb, account.client);
        expect(dryRunResult.effects.status.status).toEqual("success");
    });

    it('should work well with custom baseUrl', async () => {
        const txb = new Transaction();
        const sender = '0x03fbfd4e4ee3ca0ca58612a4919257270c035164db21d8c2cfc042ce93ee9065';
        txb.setSender(sender);
        const amount = 100e6;
        await migrateSupplyPTB(txb, wUSDC, Sui, amount, account.address, { apiKey: apiKey, baseUrl: 'https://aggregator-api.naviprotocol.io' });
        const dryRunResult = await dryRunTXB(txb, account.client);
        expect(dryRunResult.effects.status.status).toEqual("success");
    });
});

describe('show available reward', () => {
    it('should generate correct account', async () => {
        const client = new NAVISDKClient({ networkType: rpcUrl, mnemonic: mnemonic });
        const availableReward = await client.getAddressAvailableRewards('')
        console.log(availableReward)
         
    });
  }, 500000)