package asyncLog

import (
	"context"
	"fmt"
	"github.com/astaxie/beego"
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/ethclient"
	"hashrateNode/cache"
	hashrateCommon "hashrateNode/common"
	"hashrateNode/log"
	"math/big"
	"strings"
	"time"
)

const (
	LastSyncBlockKey = "lastSyncBlock"
)

var (
	pullTask   *PullEvent
	bigOne     = big.NewInt(1)
	bigTen     = big.NewInt(10)
	bigTwenty  = big.NewInt(20)
	bigThirty  = big.NewInt(30)
	bigForty   = big.NewInt(40)
	bigFifty   = big.NewInt(50)
	bigSixty   = big.NewInt(60)
	bigSeventy = big.NewInt(70)
	bigEighty  = big.NewInt(80)
	bigNinety  = big.NewInt(90)
	bighundred = big.NewInt(100)
	bigK       = big.NewInt(1000)
)

type logHandler func(log types.Log) error

type PullEvent struct {
	ctx             context.Context
	client          *ethclient.Client
	lastBlock       *big.Int
	contractList    []common.Address
	contractHandler map[common.Address]logHandler
}

func init() {
	var err error
	rpc := beego.AppConfig.String("chain_rpc_url")
	deployBlock := beego.AppConfig.String("deployedBlock")
	log.Info("connect chain rpc url:", rpc)
	pullTask = &PullEvent{contractHandler: make(map[common.Address]logHandler)}
	client, err := ethclient.Dial(rpc)
	if err != nil {
		panic(fmt.Sprintf("ethclient dial failed, err:%s", err.Error()))
	} else {
		pullTask.client = client
		lastBlock := cache.Redis.Get(LastSyncBlockKey)
		if len(lastBlock) == 0 {
			lastBlock = deployBlock
		}
		blockNumber, _ := new(big.Int).SetString(lastBlock, 10)
		pullTask.lastBlock = blockNumber
	}
	pullTask.ctx = context.Background()
	pullTask.contractList = make([]common.Address, 0)
	{
		vmCreateAddrArr := getAddress(hashrateCommon.VmCreateContract)
		for _, stakeAddr := range vmCreateAddrArr {
			if len(stakeAddr) > 0 {
				addr := common.HexToAddress(stakeAddr)
				pullTask.contractList = append(pullTask.contractList, addr)
				pullTask.contractHandler[addr] = VmCreateContractHandler
			}
		}
	}
}

func SyncLogs() {
	pullTask.GetLogs()
}

func (p *PullEvent) GetLogs() {
	query := ethereum.FilterQuery{}
	query.FromBlock = p.lastBlock
	query.ToBlock = new(big.Int).Add(p.lastBlock, big.NewInt(1))
	query.Addresses = p.contractList
	for {
		query.FromBlock = p.lastBlock
		log.Info("start filter start at ", p.lastBlock.Text(10))
		height, _ := p.client.BlockNumber(p.ctx)
		if height <= p.lastBlock.Uint64() {
			time.Sleep(time.Second)
			continue
		} else if height > 1000 && (height-1000) >= p.lastBlock.Uint64() {
			query.ToBlock = new(big.Int).Add(p.lastBlock, bigK)
		} else if height > 100 && (height-100) >= p.lastBlock.Uint64() {
			query.ToBlock = new(big.Int).Add(p.lastBlock, bighundred)
		} else if height > 10 && (height-10) >= p.lastBlock.Uint64() {
			query.ToBlock = new(big.Int).Add(p.lastBlock, bigTen)
		} else {
			query.ToBlock = new(big.Int).Add(p.lastBlock, bigOne)
		}

		allLogs, err := p.client.FilterLogs(p.ctx, query)
		if err != nil {
			log.Error("filter logs failed", err)
			continue
		}
		if len(allLogs) > 0 {
			for _, vlog := range allLogs {
				handle, exist := p.contractHandler[vlog.Address]
				if exist {
					handle(vlog)
				}
			}
		}
		p.lastBlock = new(big.Int).Add(query.ToBlock, bigOne)
		cache.Redis.Set(LastSyncBlockKey, p.lastBlock.Text(10))
	}
}

func getAddress(multiAddrArr string) []string {
	addrArr := strings.Split(multiAddrArr, ",")
	ret := make([]string, 0)
	for _, addr := range addrArr {
		if len(addr) > 0 {
			ret = append(ret, addr)
		}
	}
	return ret
}
