// Core dependencies
import { BN, Program } from "@coral-xyz/anchor";
import { PublicKey, TransactionInstruction } from "@solana/web3.js";

// Local imports
import { BasketsProgram } from "../../idl/types";
import { fetchBasketState } from "../../state/basket";
import { MAX_PRICE_UPDATES_PER_TX, PYTHNET_CUSTODY_PRICE_SOL_ACCOUNT, PYTHNET_CUSTODY_PRICE_USDC_ACCOUNT, UPDATE_PRICES_AUTHORITY } from "../../utils/constants";


async function getJupPrices(
    mints: PublicKey[],
): Promise<number[]> {
    let batchSize = 100;
    let prices: number[] = [];
    for (let i = 0; i < mints.length; i += batchSize) {
        const batch = mints.slice(i, i + batchSize);
        let str = batch.map(mint => mint.toBase58()).join(",");
        let result = await fetch(`https://api.jup.ag/price/v2?ids=${str}`);
        let data = (await result.json()).data;
        for (let j = 0; j < batch.length; j++) {
            if (data[batch[j].toBase58()])
                prices.push(data[batch[j].toBase58()].price);
            else {
                throw new Error("Price not found for " + batch[j].toBase58());
            }
        }
    }
    return prices;
}

// Helper function to create update prices instruction
async function createUpdatePricesIx(
    program: Program<BasketsProgram>,
    basket: PublicKey,
    accounts: {
        pubkey: PublicKey;
        isSigner: boolean;
        isWritable: boolean;
    }[],
    tokenPrices: BN[],
): Promise<TransactionInstruction> {
    while (tokenPrices.length < 25)
        tokenPrices.push(new BN(0));
    return program.methods
        .updateTokenPrices(tokenPrices)
        .accountsStrict({
            authority: UPDATE_PRICES_AUTHORITY,
            basket,
            usdcPriceOracle: PYTHNET_CUSTODY_PRICE_USDC_ACCOUNT,
            solPriceOracle: PYTHNET_CUSTODY_PRICE_SOL_ACCOUNT,
        })
        .remainingAccounts(accounts)
        .instruction();
};

// Update token prices instructions
export async function updateTokenPricesIxs(params: {
    program: Program<BasketsProgram>;
    basket: PublicKey;
}): Promise<{
    ixs: TransactionInstruction[],
    luts: PublicKey[],
}> {
    // Destructure params and get basket state
    const { program, basket } = params;
    const basketState = await fetchBasketState(program, basket);

    let jupPrices = await getJupPrices(basketState.compositionMints.slice(0, basketState.numTokens));
    
    // Initialize arrays to store instructions and remaining accounts
    const ixs: TransactionInstruction[] = [];
    let remainingAccounts = [];
    let tokenPrices = [];

    // Process each token's oracle accounts
    for (let i = 0; i < basketState.numTokens; i++) {
        let num = Math.floor(jupPrices[i] * 10 ** 10);
        tokenPrices.push(new BN(num).mul(new BN(100)));
    
        // Add primary oracle
        remainingAccounts.push({
            pubkey: basketState.compositionOracle1[i],
            isSigner: false,
            isWritable: false,
        });

        remainingAccounts.push({
            pubkey: basketState.compositionOracle2[i],
            isSigner: false,
            isWritable: false,
        });

        // Create instruction if we've hit the max accounts limit
        if (remainingAccounts.length + 1 >= MAX_PRICE_UPDATES_PER_TX) {
            ixs.push(await createUpdatePricesIx(program, basket, remainingAccounts, tokenPrices));
            remainingAccounts = [];
            tokenPrices = [];
        }
    }

    // Create final instruction if there are remaining accounts
    if (remainingAccounts.length > 0) {
        ixs.push(await createUpdatePricesIx(program, basket, remainingAccounts, tokenPrices));
    }

    return {
        ixs,
        luts: [basketState.lookupTable1, basketState.lookupTable2],
    };
}
