import time

from calcTools  import *
from osTools import *
from web3Tools import *




class MaxProfitUtils:
    # 判断两个数组是否有交集
    @staticmethod
    def has_intersection(arr1, arr2):
        return len(set(arr1) & set(arr2)) > 0

    # 若二维数组中所有数组与指定数组没有交集，返回True
    @staticmethod
    def all_has_no_intersection(swap_details_list, check_swap_details):
        for swap_details in swap_details_list:
            if MaxProfitUtils.has_intersection(swap_details['swapPath'], check_swap_details['swapPath']):
                return False

        return True

    # 求和swap详情list的利润并返回
    @staticmethod
    def get_profit_sum(swap_details_list):
        profit_sum = 0

        for swap_details in swap_details_list:
            profit_sum += swap_details['profit']

        return profit_sum

    @staticmethod
    def get_max_profit_swap_details_list(x, y):
        return x if MaxProfitUtils.get_profit_sum(x) > MaxProfitUtils.get_profit_sum(y) else y

    # 得出指定数据结构中的最大利润path
    @staticmethod
    def max_profit_path_list(origin_swap_details_list, level=0, swap_details_list=None):
        # first level
        if time.time() - initbestTradeListTime>60:
            printTime('max_profit_path_list loong')
        if swap_details_list is None:
            swap_details_list = []

        # last level
        # >=
        if level == len(origin_swap_details_list):
            return swap_details_list

        # 此层的swap_path与当前的swap_path_list内的所有swap_path都没有交集的话，将此层的加入为x路径
        x = []
        if MaxProfitUtils.all_has_no_intersection(swap_details_list, origin_swap_details_list[level]):
            new_swap_details_list = json.loads(json.dumps(swap_details_list))
            new_swap_details_list.append(origin_swap_details_list[level])
            x = MaxProfitUtils.max_profit_path_list(origin_swap_details_list, level+1, new_swap_details_list)

        # 不将此层加入的y路径
        y = MaxProfitUtils.max_profit_path_list(origin_swap_details_list, level+1, swap_details_list)

        return MaxProfitUtils.get_max_profit_swap_details_list(x, y)


def calOnlyLpInRouterArray(routerArray):
    onlyLpArray = []
    for router in routerArray:
        for lpInfo in router:
            lp = lpInfo['lp']
            if lp in onlyLpArray:
                continue
            if lp in nowBlockLpRes:
                continue
            onlyLpArray.append(lp)
    return onlyLpArray



def calRouterProfit(router):
    # 整理LP信息
    for lpInfo in router:
        lpAddress = lpInfo['lp']
        r0 = nowBlockLpRes[lpAddress][0]
        r1 = nowBlockLpRes[lpAddress][1]
        if lpAddress not in topLpInfo:
            return False
        lpInfo['fee'] = topLpInfo[lpAddress]['fee']
        if lpInfo['inToken'] < lpInfo['outToken']:
            lpInfo['rIn'] = r0
            lpInfo['rOut'] = r1
        else:
            lpInfo['rIn'] = r1
            lpInfo['rOut'] = r0
    # ABA
    if len(router) == 2:
        tokenLoan = router[1]['outToken']
        tokenIn0  = router[0]['inToken']
        tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] /  10 ** baseTokenInfo[tokenLoan]['decimals']
        # WETH - A - WETH
        tradeInfo = calBestAmountV2V2(router[0]['rIn'], router[0]['rOut'], router[0]['fee'], router[1]['rIn'], router[1]['rOut'], router[1]['fee'] )
        profit = tradeInfo['profit']
        tradeInfo['ethProfit'] = profit * tokenLoanETHPrice
        #printTime(router, amountIn0, amountOut0, amountOut1, profit)
        if tradeInfo['ethProfit'] < tradeMinProfit:
            return False
        else:
            tradeInfo['lp0'] = router[0]['lp']
            tradeInfo['lp1'] = router[1]['lp']
            tradeInfo['fee0'] = router[0]['fee']
            tradeInfo['fee1'] = router[1]['fee']
            tradeInfo['indexIn0'] = router[0]['indexIn']
            tradeInfo['indexIn1'] = router[1]['indexIn']
            tradeInfo['tokenLoan'] = tokenLoan
            tradeInfo['tokenIn0'] = tokenIn0
            tradeInfo['tradeToken0'] = router[0]['outToken']
            tradeInfo['type'] = 'type2'
            return tradeInfo

    # ABCA
    elif len(router) == 3:
        tokenLoan = router[2]['outToken']
        tokenIn0  = router[0]['inToken']
        tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] /  10 ** baseTokenInfo[tokenLoan]['decimals']
        tradeInfo = calBestAmountABCA(router[0]['rIn'], router[0]['rOut'], router[0]['fee'],
                                      router[1]['rIn'], router[1]['rOut'], router[1]['fee'],
                                      router[2]['rIn'], router[2]['rOut'], router[2]['fee'])
        profit = tradeInfo['profit']
        tradeInfo['ethProfit'] = profit * tokenLoanETHPrice
        #printTime(router, amountIn0, amountOut0, amountOut1, amountOut2, profit)
        if tradeInfo['ethProfit'] < tradeMinProfit:
            return False
        else:
            tradeInfo['lp0'] = router[0]['lp']
            tradeInfo['lp1'] = router[1]['lp']
            tradeInfo['lp2'] = router[2]['lp']
            tradeInfo['fee0'] = router[0]['fee']
            tradeInfo['fee1'] = router[1]['fee']
            tradeInfo['fee2'] = router[2]['fee']
            tradeInfo['indexIn0'] = router[0]['indexIn']
            tradeInfo['indexIn1'] = router[1]['indexIn']
            tradeInfo['indexIn2'] = router[2]['indexIn']
            tradeInfo['tokenLoan'] = tokenLoan
            tradeInfo['tokenIn0'] = tokenIn0
            tradeInfo['tradeToken0'] = router[0]['outToken']
            tradeInfo['tradeToken1'] = router[1]['outToken']
            tradeInfo['type'] = 'type3'
            return tradeInfo





def callABCA(lp0, lp1, lpLoan, tokenIn0, tokenLoan, fee0, fee1, fee2, lp0InIndex, lp1InIndex, lp2InIndex, minProfit, block = 'latest'):
    function = '0x1d650c35'
    inputData = encodeInput(lp0, lp1, lpLoan, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                fee0, fee1, fee2, lp0InIndex, lp1InIndex, lp2InIndex,
                tokenIn0, tokenLoan, minProfit, 0)
    inputData = function + inputData
    r = gasCal(myAddress, v3CCAddress, inputData, block)
    tradeInfo = {'type':3, 'lp0': lp0, 'lp1': lp1, 'lpLoan':lpLoan, 'tokenIn':tokenIn0, 'tokenLoan':tokenLoan, 'profit':0, 'sumValue':0}
    if len(r['info']) != 23 or r['info'][-1] == 0:
        return tradeInfo
    sumValue = r['info'][3] + r['info'][4] + r['info'][5] + r['info'][6] + r['info'][7] + r['info'][8]
    tradeInfo['sumValue'] = sumValue
    tradeInfo['profit'] = r['info'][-1]
    tradeInfo['gasUsed'] = r['gasUsed']
    tradeInfo['inputData'] = inputData
    return tradeInfo

def callABA(lp0, lp1, tokenIn0, tokenLoan, fee0, fee1, lp0InIndex, lp1InIndex, minProfit, block = 'latest'):
    # estimasGas 不能用
    function = '0xa628df6d'
    inputData = encodeInput(lp0, lp1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                fee0, fee1, 0, lp0InIndex, lp1InIndex, 0,
                tokenIn0, tokenLoan, minProfit, 0)
    inputData = function + inputData
    r = gasCal(myAddress, v3CCAddress, inputData, block)
    
    tradeInfo = {'type':2, 'lp0': lp0, 'lpLoan':lp1, 'tokenIn':tokenIn0, 'tokenLoan':tokenLoan, 'profit':0, 'sumValue':0}
    if len(r['info']) != 23 or r['info'][-1] == 0:
        return tradeInfo

    #路径中所有R0R1的和
    sumValue = r['info'][3] + r['info'][4] + r['info'][5] + r['info'][6] + r['info'][7] + r['info'][8]
    tradeInfo['sumValue'] = sumValue
    tradeInfo['profit'] = r['info'][-1]
    tradeInfo['gasUsed'] = r['gasUsed']
    tradeInfo['inputData'] = inputData
    return tradeInfo

def readABA():
    array = []
    for i in baseTokenInfo:
        i = i.lower()
        iiArray = readAny('aba' + i + i)
        array = array + iiArray
    array = array + readAny('aba' + WETHW + WETH)
    array = array + readAny('aba' + WETH + WETHW)
    return array

def readABCA():
    array = []
    for i in baseTokenInfo:
        i = i.lower()
        iiArray = readAny('abca' + i + i)
        array = array + iiArray
    array = array + readAny('abca' + WETHW + WETH)
    array = array + readAny('abca' + WETH + WETHW)
    return array


# 读取LP，LP怎么来？
topLpInfo  = readAny('topLP')
# 读取路径，路径怎么来？
lpRouterInfo = readABCA()
lpRouterInfo = lpRouterInfo + readABA()

printTime('v3 start')
v3CCAddress = '0xB4971D0dda22359Ad86867362b7FC3206eA0d86B'
dataUsed = []
nowBlockLpRes = {}
# 自定义开始区块号码
initBlock = w3.eth.blockNumber
while True:
    try:
        if time.time() % 60 > 50 and time.time() % 60 < 55:
            # 读取LP
            topLpInfo = readAny('topLP')
            # 读取路径
            lpRouterInfo = readABCA()
            lpRouterInfo = lpRouterInfo + readABA()

        blockNumber = w3.eth.blockNumber
        if blockNumber == initBlock:
            time.sleep(0.1)
            continue

        initBlock = blockNumber

        # 算出基础gas
        baseGasPrice = getBaseGasPrice(blockNumber) * 1.22
        if baseGasPrice < 1.6 * 1e9:
            baseGasPrice = 1.6 * 1e9

        nowBlockLpRes = {}
        # 找出有关该LP的路径 == 因为不限制时间，并且速度够快。可能等于所有路径。
        lpRouterArray = lpRouterInfo  # findLpRouter(nowBlockLpTradeI, lpRouterInfo)

        # 过滤重复LP。返回一次所有LP  （[ABC][ABD]）> [ABCD]
        lpOnlyRouterArray = calOnlyLpInRouterArray(lpRouterArray)

        # printTime(len(lpOnlyRouterArray), len(lpRouterArray))

        # 获取余额
        lpResArray = w3.dex.getPairSBalance(lpOnlyRouterArray, blockNumber)

        # 余额进行缓存
        for lpResI in lpResArray:
            nowBlockLpRes[lpResI] = lpResArray[lpResI]

        #计算每一个路径的利润
        tradeInfoArray = []
        initForTime = time.time()
        for lpRouter in lpRouterArray:
            if time.time() - initForTime > 60:
                printTime('lpRouterArray long time')
            #计算收入，通过本地R0R1，计算的
            tradeInfo = calRouterProfit(lpRouter)

            if tradeInfo:
                type = tradeInfo['type']

                if type == 'type2':
                    #计算利润 = 收入 - gas支出，通过智能合约计算
                    result = callABA(tradeInfo['lp0'], tradeInfo['lp1'],
                                    tradeInfo['tokenIn0'], tradeInfo['tokenLoan'],
                                    tradeInfo['fee0'], tradeInfo['fee1'],
                                    tradeInfo['indexIn0'], tradeInfo['indexIn1'],
                                    baseTokenInfo[tradeInfo['tokenIn0']]['profitMin'], blockNumber)
                    
                    # result['profit'] 是收入
                    if result['profit'] < tradeMinProfit:
                        continue
                    
                    sumValue = result['sumValue']

                    #过滤幽灵LP, 其实可以通过eth call可以排除，但是很耗时间。
                    
                    if sumValue in dataUsed:
                        continue

                    tokenLoanETHPrice = baseTokenInfo[tradeInfo['tokenLoan']]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] / 10 ** \
                                        baseTokenInfo[tradeInfo['tokenLoan']]['decimals']
                    result['ethProfit'] = result['profit'] * tokenLoanETHPrice
                    result['ethProfit-E18'] = result['profit'] * tokenLoanETHPrice / 1e18
                    # result['tureProfit'] 才是利润
                    result['tureProfit'] = result['ethProfit'] - result['gasUsed'] * baseGasPrice
                    result['maxPrice'] = result['ethProfit'] / result['gasUsed']
                    if result['tureProfit'] < tradeMinProfit:
                        continue
                    #有利润就加入交易数组
                    tradeInfoArray.append(result)

                if type == 'type3':
                    result = callABCA(tradeInfo['lp0'], tradeInfo['lp1'], tradeInfo['lp2'],
                                    tradeInfo['tokenIn0'], tradeInfo['tokenLoan'],
                                    tradeInfo['fee0'], tradeInfo['fee1'], tradeInfo['fee2'],
                                    tradeInfo['indexIn0'], tradeInfo['indexIn1'], tradeInfo['indexIn2'],
                                    baseTokenInfo[tradeInfo['tokenIn0']]['profitMin'], blockNumber)
                    if result['profit'] < tradeMinProfit:
                        continue
                    sumValue = result['sumValue']
                    if sumValue in dataUsed:
                        continue
                    tokenLoanETHPrice = baseTokenInfo[tradeInfo['tokenLoan']]['price'] * 10 ** baseTokenInfo[WETH][
                        'decimals'] / 10 ** \
                                        baseTokenInfo[tradeInfo['tokenLoan']]['decimals']
                    result['ethProfit'] = result['profit'] * tokenLoanETHPrice
                    result['ethProfit-E18'] = result['profit'] * tokenLoanETHPrice / 1e18
                    result['tureProfit'] = result['ethProfit'] - result['gasUsed'] * baseGasPrice
                    result['maxPrice'] = result['ethProfit'] / result['gasUsed']
                    if result['tureProfit'] < tradeMinProfit:
                        continue
                    tradeInfoArray.append(result)

        if len(tradeInfoArray) != 0 :
            #printTime('trade List' ,blockNumber, tradeInfoArray)
            tradeList = []
            initForTime = time.time()
            #交易详细信息数组转化为只有 LP:利润的信息。然后所有交易信息用tradeInfoI复制一次。
            for tradeInfoI in tradeInfoArray:
                if tradeInfoI['type'] == 2:
                    tradeList.append({"swapPath": [tradeInfoI['lp0'], tradeInfoI['lpLoan']], "profit": tradeInfoI['tureProfit'], 'data':tradeInfoI})
                if tradeInfoI['type'] == 3:
                    tradeList.append(
                        {"swapPath": [tradeInfoI['lp0'], tradeInfoI['lp1'],tradeInfoI['lpLoan']], "profit": tradeInfoI['tureProfit'], 'data':tradeInfoI})

            
            initbestTradeListTime = time.time( )
            # 进行利润排序，从大到小
            tradeList = sorted(tradeList, key=lambda k: k['profit'], reverse=True)
            params = []

            # 进行input的encode
            for info in tradeList:
                paramsI = {}
                paramsI['from'] = myAddress
                paramsI['to'] = '0xEAf90BcB75e7B92900678c4aBD80883b414bEDD6'
                tradeInputData = info['data']['inputData']
                paramsI['data'] = '0x4327f0db' + encodeInput(v4Address, 0x40, (len(tradeInputData) - 2) / 2, tradeInputData)
                params.append(paramsI)

            #BATCH CALL暴力解LP ，按顺序，进行一次模拟交易。是一直叠加态
            batchCallResult = batchCall(params, blockNumber)     
            batchCallResult = decodeBatchCall(batchCallResult['result']) 
            totalProfit = 0
            length = len(tradeList)
            bestIndex = []
            for i in range(0, length):
                profit = batchCallResult[i][-1]
                if profit > 0:
                    tokenLoan = batchCallResult[i][-3]
                    gasUsed = batchCallResult[i][1]
                    tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH][
                        'decimals'] / 10 ** \
                                        baseTokenInfo[tokenLoan]['decimals']
                    profit = profit * tokenLoanETHPrice
                    # 最大gasPrice
                    maxPrice = profit / gasUsed  
                    profit = profit - gasUsed * baseGasPrice  
                    tradeList[i]['data']['maxPrice'] = maxPrice
                    tradeList[i]['data']['profit'] = profit
                    if profit < tradeMinProfit:
                        continue                 
                    totalProfit = totalProfit + profit   
                    # 成功的位置做key 
                    bestIndex.append(i) 

            printTime(blockNumber, 'batch', time.time() - initbestTradeListTime, 's , totalProfit', totalProfit/1e18)
            # 过滤要下单的路径
            bestTradeList = []
            for index in bestIndex:
                bestTradeList.append(tradeList[index])
            tradeTimes = 0
            initForTime = time.time()
            # 根据路径下单
            for bestTradeI in bestTradeList:
                if time.time() - initForTime > 60:
                    printTime('bestTradeList long time')
                printTime(blockNumber, 'bset', bestTradeI['swapPath'], bestTradeI['profit']/1e18)
                sumValue = bestTradeI['data']['sumValue']
                inputData = bestTradeI['data']['inputData']

                nowGasPrice = bestTradeI['data']['maxPrice'] * 0.2

                if nowGasPrice < baseGasPrice *1.2:
                    nowGasPrice = baseGasPrice * 1.2
                if nowGasPrice > baseGasPrice * 1.8:
                    nowGasPrice = baseGasPrice * 1.8

                # 为了后面gas war，做传参。
                gasLimit = 1e6 + bestTradeI['data']['maxPrice'] / 1e9 + 1
                if tradeTimes > 0:
                     gasLimit = 1e6

                
                if gasLimit > 1e6 + 2000:
                    gasLimit = 1e6 + 2000
                
                params = {}
                params['method'] = 'send'
                params['params'] = {'toAddress': v3CCAddress, 'inputData': inputData,
                                    'gasPrice': nowGasPrice, 'gasLimit': gasLimit}
                try:
                    hs = requests.post(url='http://127.0.0.1:410/operation', json=params).text
                    if len(hs)>50:
                        printTime(blockNumber, 'https://www.oklink.com/en/ethw/tx/' + str(hs))
                        dataUsed.append(sumValue)
                        tradeTimes = tradeTimes + 1
                except:
                    hs = 'err seed'

            printTime('-' * 50)
    except BaseException as err:
        printTime(traceback.format_exc())
        printTime('loop', )
        time.sleep(20)








        
