package multicaller

import (
	"math/big"
	"portfolio/configs"
	Multicall "portfolio/contracts/MulticallContract"
	"portfolio/core/utils"
	"portfolio/schema"
	"sync/atomic"

	"github.com/ethereum/go-ethereum/common"
	log "github.com/sirupsen/logrus"
)

var (
	TokenBalanceCallOpt ChunkedCallOpts
)

func init() {
	TokenBalanceCallOpt = ChunkedCallOpts{W3CallOpt: nil, ChunkSize: 5}
}

// getTokenBalances Wallet balance based on given token ( Faster if chunks is used)
// Does not sort + only respond with tokens with balance
func getTokenBalances(
	callOpts ChunkedCallOpts,
	id schema.ChainId,
	multiCaller Multicall.MulticallCaller,
	tokens schema.TokenMapping,
	wallet common.Address,
	chunkedResultChannel chan []ChunkCall[*big.Int]) uint64 {

	allCalls := genTokenBalanceCalls(tokens, wallet)
	chunkedCalls := utils.Chunks[ChunkCall[*big.Int]](allCalls, callOpts.ChunkSize)

	for i, indexCalls := range chunkedCalls {
		cachedChunkCalls := ChunkCallsCache.Get(ChunkCallsCacheKey{wallet, id, i, "t"})
		if cachedChunkCalls != nil && !cachedChunkCalls.IsExpired() {
			go func() {
				chunkedResultChannel <- cachedChunkCalls.Value()
			}()
		} else {
			go execute("t", i, id, wallet, multiCaller, indexCalls, chunkedResultChannel)
		}
	}

	return uint64(len(chunkedCalls))
}

func balanceTokenResultParser(wallet common.Address, result map[schema.ChainId]schema.TokenMapping, chunk []ChunkCall[*big.Int]) {
	for _, call := range chunk {

		// In case error occurred at rpc level
		if call.Err != nil {
		}

		if !call.CallRes.Success || call.ParsedCallRes == nil {
			continue
		}
		chainId := call.ChainId
		if result[chainId] == nil {
			result[chainId] = make(schema.TokenMapping)
		}
		_tokenId := schema.TokenId(call.Id)
		_token := configs.ChainTokens(chainId)[_tokenId].Copy()

		// Token Balance
		decimal := configs.DecimalPowTen(_token.Detail.Decimals)
		b := new(big.Float).SetInt(call.ParsedCallRes)
		b = b.Quo(b, new(big.Float).SetInt(decimal))
		if b.IsInf() {
			log.Errorf("[INF] @ (%d,%s) ::: cnResp  %s ", _token.Detail.Decimals, _token.Detail.Address, call.ParsedCallRes)
		}
		_token.Balance = *b
		_token.BalanceStr = b.String()

		if _token.PriceUSD != 0 {
			v := new(big.Float)
			v.Copy(b)
			v.Mul(v, big.NewFloat(_token.PriceUSD))

			_token.Value = *v
			_token.ValueStr = v.String()
		}
		result[chainId][_tokenId] = *_token

	}

}

func GetChainsTokenBalances(
	chainIds []schema.ChainId,
	wallet common.Address) map[schema.ChainId]schema.TokenMapping {

	chunkedResultChannel := make(chan []ChunkCall[*big.Int])
	_res := make(map[schema.ChainId]schema.TokenMapping)

	var totalChunkCount uint64
	totalChunkCount = 0

	for _, chainId := range chainIds {
		_tokens := configs.ChainTokens(chainId)
		_multicall := configs.ChainMultiCall(chainId)

		if _multicall == nil || _tokens == nil {
			continue
		}
		atomic.AddUint64(
			&totalChunkCount,
			getTokenBalances(TokenBalanceCallOpt, chainId, *_multicall, _tokens, wallet, chunkedResultChannel))
	}
	if totalChunkCount == 0 {
		return _res
	}
	for chunkCalls := range chunkedResultChannel {
		if totalChunkCount > 0 {
			totalChunkCount--
		}
		balanceTokenResultParser(wallet, _res, chunkCalls)
		if totalChunkCount == 0 {
			break
		}
	}

	close(chunkedResultChannel)

	return _res
}
