package blockchain

import (
	"context"
	"encoding/hex"
	"fmt"
	"ktogame/contractgo/Collect"
	"ktogame/controller"
	"ktogame/dbUtil"
	"ktogame/models"
	"os"
	"strings"
	"time"

	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"github.com/ethereum/go-ethereum/accounts/abi"

	utilAddress "github.com/korthochain/korthochain/pkg/address"
	"github.com/korthochain/korthochain/pkg/block"
	pb "github.com/korthochain/korthochain/pkg/server/grpcserver/message"
	"github.com/korthochain/korthochain/pkg/transaction"
	"google.golang.org/grpc"
)

var ktoClient pb.GreeterClient
var ktoRpc = beego.AppConfig.String("miner_node")
var currentBlock uint64

var TopsMap map[string]string

func init() {
	kc, err := grpc.Dial(ktoRpc, grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		logs.Error(err)
		os.Exit(1)
	}
	ktoClient = pb.NewGreeterClient(kc)
	utilAddress.SetNetWork("mainnet")

	tm := make(map[string]string, 0)
	tm[TOPIC_PARTICIPATE] = COLLECT_PARTICIPATE
	tm[TOPIC_CLAIM] = COLLECT_CLAIMREWARDS

	TopsMap = tm

	go scanBlock()
	go confirmClaimedTxs()
}

func scanBlock() {
	logs.Info("scanBlock...")
	var bi models.BlockInfo
	ok, err := dbUtil.Engine.Id(1).Get(&bi)
	if err != nil {
		fmt.Println("获取最新快高错误=", err)
		return
	}
	if !ok {
		fmt.Println("获取最新快高失败!")
		return
	}
	currentBlock = uint64(bi.BlockNumber) + 1
	//currentBlock =62203394
	for {
		time.Sleep(time.Second * 3)

		res, err := ktoClient.GetMaxBlockHeight(context.Background(), &pb.ReqMaxBlockHeight{})
		if err != nil {
			logs.Error("获取交易block number错误=", err)
			continue
		}
		logs.Info("currentBlock:", currentBlock, "chainblock:", res.MaxHeight)
		if res.MaxHeight < currentBlock {
			currentBlock = res.MaxHeight
		}

		bl, err := ktoClient.GetBlockByNum(context.Background(), &pb.ReqBlockByNumber{Height: currentBlock})
		if err != nil || bl.Code != 0 {
			logs.Error("获取交易block错误=", err)
			continue
		}
		blc, errs := block.Deserialize(bl.Data)
		if errs != nil {
			logs.Error("解析blcock错误=", errs)
			continue
		}
		var ERR error
		for _, v := range blc.Transactions {
			if len(v.Input) == 0 {
				continue
			}
			evm, err := transaction.DecodeEvmData(v.Input)
			if err != nil {
				logs.Error(err)
				ERR = err
				break
			}
			if len(evm.Logs) == 0 {
				continue
			}
			for _, l := range evm.Logs {
				th := l.Topics[0].Hex()
				method := TopsMap[th]
				contract := l.Address.String()
				if contract == COLLECTCONTRACT {
					if method == COLLECT_PARTICIPATE {
						var ev EventParticipate
						abi, err := abi.JSON(strings.NewReader(Collect.CollectMetaData.ABI))
						if err != nil {
							logs.Error(err)
							ERR = err
							break
						}

						err = abi.UnpackIntoInterface(&ev, method, l.Data)
						if err != nil {
							logs.Error(err)
							ERR = err
							break
						}
						logs.Info("participate data:", ev.Participant.String(), ev.Inviter.String(), v.HashToString(), float64(ev.Amount.Uint64()/controller.Decimals))
						//handle user participate
						err = participate(dbUtil.Engine, ev.Participant.String(), ev.Inviter.String(), v.HashToString(), float64(ev.Amount.Uint64()/controller.Decimals))
						if err != nil {
							logs.Error(err)
							ERR = err
							break
						}
					} else if method == COLLECT_CLAIMREWARDS {
						var evt EventClaim
						abi, err := abi.JSON(strings.NewReader(Collect.CollectMetaData.ABI))
						if err != nil {
							logs.Error(err)
							ERR = err
							break
						}
						err = abi.UnpackIntoInterface(&evt, method, l.Data)
						if err != nil {
							logs.Error(err)
							ERR = err
							break
						}
						fmt.Printf("claim event data=%+v\n ", evt)

						var bts []byte
						for _, b := range evt.Signature {
							bts = append(bts, b)
						}

						err = checkClaim(dbUtil.Engine, evt.User.String(), v.HashToString(), hex.EncodeToString(bts), float64(evt.Amount.Uint64()/controller.Decimals), int64(v.BlockNum))
						if err != nil {
							logs.Error(err)
							ERR = err
							break
						}
					}
				}

			}
			if ERR != nil {
				logs.Error(err)
				break
			}
		}
		if ERR != nil {
			logs.Error("处理错误=", ERR)
			continue
		}
		bi.BlockNumber = int64(currentBlock)
		_, err = dbUtil.Engine.ID(1).Update(&bi)
		if err != nil {
			logs.Error("更新最新快高错误=", err)
			return
		}
		currentBlock++
	}
}

func confirmClaimedTxs() {
	var mark int64 = 0
	for {
		time.Sleep(time.Second * 5)
		logs.Info("confirmClaimedTxs mark= %v\n", mark)
		var txs []models.ClaimedTxs
		err := dbUtil.Engine.Where("state = ?", 0).Where("droped = ?", 0).Where("id > ?", mark).Find(&txs)
		if err != nil {
			logs.Error(err)
			continue
		}
		if len(txs) > 0 {
			mark = txs[len(txs)-1].Id
		}
		for _, t := range txs {
			go func(tx models.ClaimedTxs) {
				interval := 0
				for {
					time.Sleep(time.Second * 5)
					if interval > (CONFIRMINTERVAL / 5) {
						var ui models.UserInfo
						_, err := dbUtil.Engine.Id(tx.Hash).Get(&ui)
						if err != nil {
							mark = tx.Id - 1
							return
						}
						ui.AvailableClaim += tx.Amount
						ui.TotalClaimed -= tx.Amount
						_, err = dbUtil.Engine.ID(tx.Addr).Cols("total_claimed,available_claim").Update(&ui)
						if err != nil {
							mark = tx.Id - 1
							return
						}

						tx.Droped = 1
						_, err = dbUtil.Engine.ID(tx.Hash).Cols("droped").Update(&tx)
						if err != nil {
							mark = tx.Id - 1
							return
						}
						return
					}
					ptx, err := ktoClient.GetTxByHash(context.Background(), &pb.ReqTxByHash{Hash: tx.Hash})
					if err != nil {
						interval++
						continue
					}
					if ptx.Code != 0 {
						interval++
						continue
					}
					ftx, err := transaction.DeserializeFinishedTransaction(ptx.Data)
					if err != nil {
						interval++
						continue
					}
					//pending tx
					if ftx.BlockNum == 0 {
						interval++
						continue
					}

					tx.State = 1
					_, err = dbUtil.Engine.ID(tx.Hash).Cols("state").Update(&tx)
					if err != nil {
						mark = tx.Id - 1
					}
					return
				}
			}(t)
		}
	}
}