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"
	"github.com/33cn/chain33/common"
	"strings"
	"chain33-pai/pkg/e"
)

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

			err = models.InitHeight()
			height = -1
			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)
				openlist := make([]*models.RaspOpen,0)
				checkList := make(map[int64]int64,0)
				var wg sync.WaitGroup
				var mutx sync.Mutex
				logging.Info("start startHeight:",processHeight," endHeight:",processHeight+maxDownload-1)
				for height := processHeight ;height <  processHeight + maxDownload;height ++ {
					wg.Add(1)
					go func(height int64) {
						//defer wg.Done()
						stat,txs,open,err := DealBlock(height)
						if err != nil {
							logging.Error("DealBlock err",err)
							panic(err)
						}
						mutx.Lock()
						logging.Info("openlist",len(open))
						openlist = append(openlist,open...)
						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
								if statMap[vv.Addr].Height < vv.Height {
									statMap[vv.Addr].Height = vv.Height
									statMap[vv.Addr].Time = vv.Time
								}
							}
						}

						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)  {
					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,openlist,processHeight+maxDownload-1)
				if err != nil {
					panic(err)
				}
				prev.LocalHeight += maxDownload
				logging.Info("end  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,[]*models.RaspOpen,error) {
	logging.Info("dealheightstart",height)
	t1 := time.Now().Unix()
	blocks ,err := client.GetBlocks(&types.ReqBlocks{Start:height,End:height,IsDetail:true})
	t2 := time.Now().Unix()
	if err != nil {
		logging.Error("GetBlocks err",err)
		return nil,nil,nil,err
	}
	block := blocks.GetItems()[0]
	rsMap := make(map[string]*RMinerStat,0)
	openList := make([]*models.RaspOpen,0)
	rt := &RMinerTxs{}
	//处理交易数据
	for k,v := range block.Block.Txs {
		if block.Receipts[k].Ty == types.ExecOk && string(v.Execer) == "ticket"{
			rt1,rss,open,err := dealTx(v,block.Receipts[k],block.Block.Height,block.Block.BlockTime)
			if err != nil {
				logging.Error("dealTx err ",err)
				return nil,nil,nil,err
			}
			if open != nil && open.Hash != "" {
				openList = append(openList,open)
			}
			if rt1 != nil && rt1.Miner != ""{
				rt.Miner = rt1.Miner
				rt.ReturnAddr = rt1.ReturnAddr
				rt.Amount = rt1.Amount
				rt.Hash = rt1.Hash
			}
			for _,vv := range rss {
				if vv != nil && vv.Addr != "" {
					rs := &RMinerStat{}
					if _,ok := rsMap[vv.Addr];!ok {
						rs.Addr = vv.Addr
						rs.MinedTicketCount = vv.MinedTicketCount
						rs.MiningTicketCount = vv.MiningTicketCount
						rs.MinedAmount = vv.MinedAmount
						rs.Time = block.Block.BlockTime
						rs.Height = block.Block.Height
						rsMap[vv.Addr] = rs
					} else {
						rsMap[vv.Addr].MiningTicketCount += vv.MiningTicketCount
						rsMap[vv.Addr].MinedAmount += vv.MinedAmount
						rsMap[vv.Addr].MinedTicketCount += vv.MinedTicketCount
						if rsMap[vv.Addr].Height < vv.Height {
							rsMap[vv.Addr].Height = vv.Height
							rsMap[vv.Addr].Time = vv.Time
						}
					}
				}
			}

		}
	}
	t3 := time.Now().Unix()
	rt.Height = block.Block.Height
	rt.Time = block.Block.BlockTime
	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("dealheightend",height,"getblock cost ",t2-t1," dealtx cost ",t3-t2)
	//logging.Info("txs",*raspTxs)

	return raspStatList,raspTxs,openList,nil
}

type RMinerTxs struct {
	Miner   string `json:"miner"`
	ReturnAddr string `json:"return_addr"`
	Amount int64 `json:"amount"`
	Height int64 `json:"height"`
	Hash string `json:"hash"`
	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,receipt *types.ReceiptData,height,blocktime int64) (*RMinerTxs,[]*RMinerStat,*models.RaspOpen,error) {
	var rt RMinerTxs
	rss := make([]*RMinerStat,0)
	open := &models.RaspOpen{}
	//只处理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,nil,err
		}
		switch miner.Ty {

		case ttype.TicketActionMiner:
			rt1,rs1,err := DealMinerAction(tx,&miner)
			if err != nil {
				return nil,nil,nil,err
			}
			rt = *rt1
			rt.Height = height
			rt.Time = blocktime
			rss = append(rss,rs1)
		case ttype.TicketActionClose:
			rslist ,err := DealCloseAction(&miner)
			if err != nil {
				return nil,nil,nil,err
			}
			for _,v:= range rslist {
				v.Time = blocktime
				v.Height = height
			}
			rss = append(rss,rslist...)
		case ttype.TicketActionOpen:
			rs1 ,err := DealOpenAction(&miner)
			if err != nil {
				return nil,nil,nil,err
			}
			rs1.Height = height
			rs1.Time = blocktime
			open.Hash = common.ToHex(tx.Hash())
			open.Miner = miner.GetTopen().MinerAddress
			open.ReturnAddr = miner.GetTopen().ReturnAddress
			rss = append(rss,rs1)
		case ttype.TicketActionGenesis:
			rs1 ,err := DealGenesisAction(&miner)
			if err != nil {
				return nil,nil,nil,err
			}
			open.Hash = common.ToHex(tx.Hash())
			open.Miner = miner.GetGenesis().MinerAddress
			open.ReturnAddr = miner.GetGenesis().ReturnAddress
			rs1.Time = blocktime
			rs1.Height = height
			rss = append(rss,rs1)
		}
	}
	logging.Info("open tx",*open)
	return &rt,rss,open,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
	r.Hash = rt.Hash
	return &r
}

func DealMinerAction(tx *types.Transaction,miner *ttype.TicketAction)(*RMinerTxs,*RMinerStat,error) {
	var rs RMinerStat
	var rt RMinerTxs
	ticketparams := strings.Split(miner.GetMiner().TicketId,":")
	if len(ticketparams) < 3 {
		return nil,nil,e.ErrWrongTicketId
	}
	realaddress ,err := GetRealAddress(ticketparams[1])
	if err != nil {
		return nil,nil,err
	}
	rt.Amount = miner.GetMiner().Reward
	rt.Miner = tx.From()
	rt.ReturnAddr = realaddress
	rt.Hash = common.ToHex(tx.Hash())
	rs.MinedAmount = miner.GetMiner().Reward
	rs.MinedTicketCount = int64(1)
	rs.Addr = realaddress
	return &rt,&rs,nil
}

func DealOpenAction(open *ttype.TicketAction)(*RMinerStat,error) {
	var rs RMinerStat
	rs.Addr = open.GetTopen().ReturnAddress
	rs.MiningTicketCount = int64(open.GetTopen().Count)
	return &rs,nil
}

func DealGenesisAction(genesis *ttype.TicketAction)(*RMinerStat,error) {
	var rs RMinerStat
	rs.Addr = genesis.GetGenesis().ReturnAddress
	rs.MiningTicketCount = int64(genesis.GetGenesis().Count)
	return &rs,nil
}

func DealCloseAction(close *ttype.TicketAction)([]*RMinerStat,error) {
	rslist := make([]*RMinerStat,0)
	for _,v := range close.GetTclose().TicketId {
		var rs RMinerStat
		ticketparams := strings.Split(v,":")
		if len(ticketparams) < 3 {
			return nil,e.ErrWrongTicketId
		}
		realaddress ,err := GetRealAddress(ticketparams[1])
		if err != nil {
			return nil,err
		}
		rs.Addr = realaddress
		rs.MiningTicketCount = int64(-1)
		rslist = append(rslist,&rs)
	}
	return rslist,nil
}

func GetRealAddress(hash string) (string,error) {
	client := &chain33.PaiClient{}
	hashb ,err := common.FromHex(hash)
	if err != nil {
		return "",err
	}
	addr ,err := models.GetReturnAddr(hash)
	if err != nil  {
		if err != gorm.ErrRecordNotFound {
			return "",err
		} else {
			res,err := client.QueryTransaction(hashb)
			if err != nil {
				return "",err
			}
			var payload ttype.TicketAction
			err = types.Decode(res.Tx.Payload,&payload)
			if err != nil {
				return "",err
			}
			switch payload.Ty {
			case ttype.TicketActionOpen:
				addr =  payload.GetTopen().ReturnAddress
			case ttype.TicketActionGenesis:
				addr = payload.GetGenesis().ReturnAddress
			default :
				return "",types.ErrActionNotSupport
			}
		}
	}
	return addr ,nil

}

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