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
				var mutx sync.Mutex
				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)
						}
						mutx.Lock()
						for _, vv:= range stat {
							if _,ok := statMap[vv.Addr];!ok {
								statMap[vv.Addr] = vv
							} else {
								statMap[vv.Addr].MinedTicketCount += vv.MinedTicketCount
								statMap[vv.Addr].MiningTicketCount += vv.MiningTicketCount
								statMap[vv.Addr].MinedAmount += vv.MinedAmount
							}
						}

						txsList = append(txsList,txs)
						mutx.Unlock()
						logging.Info("DealBlock",height)
						checkList[height] = height
						wg.Done()
					}(height)
				}
				//db.SetMainNetHeight(int(processHeight + types.MaxProcessBlocks + 1))
				wg.Wait()

				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(statMap) {
					logging.Error("txsList",len(txsList),"txsMap",len(txsMap),"statMap",len(statMap),"checkList",len(checkList))
					for _,v:= range checkList {
						var t bool
						for _, vv:= range txsList {
							if v == vv.Height {
								t = true
								break
							}
						}

						if !t {
							logging.Error("++++++++",v)
						}
					}
					for _, vv:= range txsList {
						logging.Info("txslist:",*vv)
					}
					for _, vv:= range statMap {
						logging.Info("statMap:",*vv)
					}
					panic("exception txs not eq expect")
				}
				err := models.UpdateBlocks(txsMap,statMap,processHeight+maxDownload-1)
				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]
	rsMap := make(map[string]*RMinerStat,0)
	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 && rs1.Addr != "" {
				rs := &RMinerStat{}
				if _,ok := rsMap[rs1.Addr];!ok {
					rs.Addr = rs1.Addr
					rs.MinedTicketCount = rs1.MinedTicketCount
					rs.MiningTicketCount = rs1.MiningTicketCount
					rs.MinedAmount = rs1.MinedAmount
					rs.Time = block.Block.BlockTime
					rs.Height = block.Block.Height
					rsMap[rs1.Addr] = rs
				} else {
					rsMap[rs1.Addr].MiningTicketCount += rs1.MiningTicketCount
				}
			}
		}
	}

	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 {
	//
	//		}
	//	}
	//
	//}
	raspStatList := make([]*models.RaspMinerStat,0)
	for _,v := range rsMap {
		raspStat := NewMinerStat(v)
		raspStatList = append(raspStatList,raspStat)
		logging.Info("stat",*raspStat)
	}

	raspTxs := NewMinerTxs(rt)

	logging.Info("txs",*raspTxs)
	//err = models.UpdateBlock(raspTxs,raspStat)
	//if err != nil {
	//	logging.Error("UpdateBlock err ",err)
	//	return err
	//}
	return raspStatList,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 = int64(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)
			rs.Addr = tx.From()
		}
	}

	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
}