package miner

import (
	"chain33-pai/models"
	"sync"
	"chain33-pai/pkg/chain33"
	"chain33-pai/pkg/logging"
	"chain33-pai/pkg/setting"
	"time"
	"github.com/33cn/chain33/types"
	ttype "github.com/33cn/plugin/plugin/dapp/ticket/types"
	"github.com/jinzhu/gorm"
)

func Setup() {
	height,err := models.GetHeight()
	if err != nil {
		if err == gorm.ErrRecordNotFound {

			err = models.InitHeight()
			if err != nil {
				panic(err)
			}
		} else {
			panic(err)
		}
	}
	logging.Info("start height:",height)
	prev.LocalHeight = height
	prev.SetMainHeight()
	prev.DownloadHeight = int64(1)
	if setting.BlockSetting.DownloadHeight > 0 {
		prev.DownloadHeight = setting.BlockSetting.DownloadHeight
	}

}

var (
	prev prevHeight
	client chain33.PaiClient
)
type prevHeight struct {
	Height int64
	LocalHeight int64
	DownloadHeight int64
	lock sync.Mutex
}

func SyncBlock() {
	logging.Info("init prevheight",prev)
	for {
		var t time.Duration
		prev.lock.Lock()
		lastHeight := prev.Height
		startHeight := prev.LocalHeight
		maxDownload := prev.DownloadHeight
		if lastHeight > startHeight {
			if lastHeight - startHeight < prev.DownloadHeight {
				maxDownload = 1
			}
			//速度模式
			for processHeight := startHeight + 1; processHeight <= lastHeight; processHeight += maxDownload {
				statMap := make(map[string]*models.RaspMinerStat,0)
				txsMap := make(map[int64]*models.RaspMinerTxs,0)
				statList := make([]*models.RaspMinerStat,0)
				txsList := make([]*models.RaspMinerTxs,0)
				checkList := make(map[int64]int64,0)
				var wg sync.WaitGroup
				for height := processHeight ;height <  processHeight + maxDownload;height ++ {
					wg.Add(1)
					go func(height int64) {
						defer wg.Done()
						stat,txs,err := DealBlock(height)
						if err != nil {
							logging.Error("DealBlock err",err)
							panic(err)
						}
						statList = append(statList,stat)
						txsList = append(txsList,txs)
						logging.Info("DealBlock",height)
						logging.Info("DealBlock stat",*stat)
						logging.Info("DealBlock txs",*txs)
						checkList[height] = height
						time.Sleep(time.Millisecond*10)
					}(height)
				}
				//db.SetMainNetHeight(int(processHeight + types.MaxProcessBlocks + 1))
				wg.Wait()
				for _,v := range statList {
					if v != nil && v.Addr != "" {
						if _,ok:= statMap[v.Addr];!ok {
							statMap[v.Addr] = v
						} else {
							temp := statMap[v.Addr]
							statMap[v.Addr] = &models.RaspMinerStat{
								Addr:v.Addr,
								MiningTicketCount:v.MiningTicketCount+temp.MiningTicketCount,
								MinedTicketCount:v.MinedTicketCount+temp.MinedTicketCount,
								MinedAmount:v.MinedAmount+temp.MinedAmount,
								Height:v.Height,
								Time:v.Time,
							}
						}
					}
				}
				for _,v := range txsList {
					if v != nil && v.Height >0 {
						if _, ok := txsMap[v.Height];!ok {
							txsMap[v.Height] = v
						}
					}
				}
				if len(txsList) != int(maxDownload) || len(txsList) != len(statList) {
					logging.Error("txsList",len(txsList),"txsMap",len(txsMap),"statList",len(statList))
					for _,v:= range checkList {
						var t, s  bool
						for _, vv:= range txsList {
							if v == vv.Height {
								t = true
								break
							}
						}
						for _, vv:= range statList {
							if v == vv.Height {
								s = true
								break
							}
						}
						if !t || !s {
							logging.Error("++++++++",v)
						}
					}
					for _, vv:= range txsList {
						logging.Info("txslist:",*vv)
					}
					for _, vv:= range statList {
						logging.Info("statList:",*vv)
					}
					panic("exception txs not eq expect")
				}
				err := models.UpdateBlocks(txsMap,statMap,processHeight+maxDownload)
				if err != nil {
					panic(err)
				}
				prev.LocalHeight += maxDownload
				logging.Info("startHeight:",processHeight," endHeight:",processHeight+maxDownload-1)
			}


			t = time.Millisecond * 50
		} else {
			prev.SetMainHeight()
			t = time.Second * 5
		}
		prev.lock.Unlock()
		logging.Info("prevHeight",prev)
		time.Sleep(t)
	}

}


func (prev *prevHeight) SetMainHeight() error {

	header,err := client.GetLastHeader()
	if err != nil {
		logging.Error("GetLastHeader err ",err)
		return err
	}
	prev.Height = header.Height
	return nil
}


func  DealBlock(height int64) ( *models.RaspMinerStat, *models.RaspMinerTxs,error) {
	blocks ,err := client.GetBlocks(&types.ReqBlocks{Start:height,End:height,IsDetail:true})
	if err != nil {
		logging.Error("GetBlocks err",err)
		return nil,nil,err
	}
	block := blocks.GetItems()[0]
	rs := &RMinerStat{}
	rt := &RMinerTxs{}
	//处理交易数据
	for k,v := range block.Block.Txs {
		if block.Receipts[k].Ty == types.ExecOk {
			rt1,rs1,err := dealTx(v)
			if err != nil {
				logging.Error("dealTx err ",err)
				return nil,nil,err
			}
			if rt1 != nil && rt1.Miner != ""{
				rt.Miner = rt1.Miner
				rt.Amount = rt1.Amount
			}
			if rs1 != nil {
				rs.MinedTicketCount += rs1.MinedTicketCount
				rs.MiningTicketCount += rs1.MiningTicketCount
				rs.MinedAmount += rs1.MinedAmount
				rs.Addr = rs1.Addr
			}
		}
	}
	rs.Time = block.Block.BlockTime
	rs.Height = block.Block.Height
	rt.Height = block.Block.Height
	rt.Time = block.Block.BlockTime
	//处理交易日志
	//for _,v := range block.Receipts {
	//	if v.Ty == types.ExecOk {
	//		logs := v.GetLogs()
	//		for _, vv := range logs {
	//
	//		}
	//	}
	//
	//}
	raspStat := NewMinerStat(rs)
	raspTxs := NewMinerTxs(rt)
	//err = models.UpdateBlock(raspTxs,raspStat)
	//if err != nil {
	//	logging.Error("UpdateBlock err ",err)
	//	return err
	//}
	return raspStat,raspTxs,nil
}

type RMinerTxs struct {
	Miner   string `json:"miner"`
	ReturnAddr string `json:"return_addr"`
	Amount int64 `json:"amount"`
	Height int64 `json:"height"`
	Time int64 `json:"time"`
}


type RMinerStat struct {
	Addr   string `json:"addr"`
	MinedAmount int64 `json:"mined_amount"`
	MinedTicketCount int64 `json:"mined_ticket_count"`
	MiningTicketCount int64 `json:"mining_ticket_count"`
	Total int64 `json:"total"`
	Balance int64 `json:"balance"`
	Frozen int64 `json:"frozen"`
	Height int64 `json:"height"`
	Time int64 `json:"time"`
}

func dealTx(tx *types.Transaction) (*RMinerTxs,*RMinerStat,error) {
	var rt RMinerTxs
	var rs RMinerStat
	//只处理ticket合约
	if string(tx.Execer) == "ticket" {
		var miner ttype.TicketAction
		err := types.Decode(tx.Payload,&miner)
		if err != nil {
			logging.Error("decode ticket err ",err)
			return nil,nil,err
		}
		switch miner.Ty {
		case ttype.TicketActionMiner:
			rt.Amount = miner.GetMiner().Reward
			rt.Miner = tx.From()
			rs.MinedAmount = miner.GetMiner().Reward
			rs.MinedTicketCount = 1
			rs.Addr = tx.From()
		case ttype.TicketActionClose:
			rs.MiningTicketCount -= int64(len(miner.GetTclose().TicketId))
			rs.Addr = tx.From()
		case ttype.TicketActionOpen:
			rs.MiningTicketCount += int64(miner.GetTopen().Count)
			rs.Addr = tx.From()
		case ttype.TicketActionGenesis:
			rs.MiningTicketCount += int64(miner.GetGenesis().Count)
		}
	}

	return &rt,&rs,nil
}

func NewMinerStat(rs *RMinerStat) *models.RaspMinerStat {
	var r models.RaspMinerStat
	r.Height = rs.Height
	r.Time = rs.Time
	r.MinedAmount = rs.MinedAmount
	r.MiningTicketCount = rs.MiningTicketCount
	r.MinedTicketCount = rs.MinedTicketCount
	r.Addr = rs.Addr
	return &r
}

func NewMinerTxs(rt *RMinerTxs) *models.RaspMinerTxs {
	var r models.RaspMinerTxs
	r.Time = rt.Time
	r.Height = rt.Height
	r.Amount = rt.Amount
	r.Miner = rt.Miner
	return &r
}

func bityuanMiner(height int64) int64 {
	// ForkChainParamV2
	if height >= 2270000 {
		return 8 * types.Coin
	} else if height > 0 {
		return 30 * types.Coin
	}
	return 0
}