package cron

import (
	"context"
	"errors"
	"time"

	"github.com/33cn/chain33/common"
	"github.com/33cn/chain33/types"
	"go.uber.org/zap"
	"courseSign/server/global"
	"courseSign/server/model/airDrop"
	"courseSign/server/model/entrustPool"
	"courseSign/server/model/twi"
	"courseSign/server/utils/chain33"
)

type SendAirDropTx struct {
	JobName    string
	Client     types.Chain33Client
	LastHeight int64
	Users map[int64]*twi.TwiMetamask
}

var sendJobRun bool

// 0、检查节点状态 检查配置地址
// 1、检查未发送 发送交易
// 2、检查失败交易 重新发送
// 3、检查交易是否超时 超时标记失败
func (job SendAirDropTx) Run() {
	job.JobName = "SendAirDropTx"
	if sendJobRun {
		global.GVA_LOG.Info(job.JobName,zap.String("job","already run"))
		return
	}
	sendJobRun = true
	global.GVA_LOG.Info(job.JobName, zap.String("job", "start"))
	t := time.Now()
	defer func() {
		sendJobRun = false
		global.GVA_LOG.Info(job.JobName, zap.String("job", "end"))
		global.GVA_LOG.Info(job.JobName, zap.String("cost", time.Since(t).String()))
	}()
	var err error
	job.Users = make(map[int64]*twi.TwiMetamask,0)
	job.Client = global.Client
	if global.GVA_CONFIG.BlockChain.Priv == "" || global.GVA_CONFIG.BlockChain.Address == "" {
		global.GVA_LOG.Error(job.JobName, zap.String("config empty", "priv address"))
		return
	}
	err = job.SetVars()
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("send set vars err", err.Error()))
		return
	}
	// 发送红包金额
	err = job.SendRedPackageTxs()
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("send red package txs err", err.Error()))
		return
	}

	// 发送任务奖励
	err = job.SendActionTxs()
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("send action txs err", err.Error()))
		return
	}

	// 发送矿池投票奖励
	err = job.SendPoolReward()
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("SendPoolReward txs err", err.Error()))
		return
	}
}

func (job SendAirDropTx) SetVars() error {
	list := make([]*twi.TwiMetamask,0)
	err := global.GVA_DB.Model(&twi.TwiMetamask{}).Find(&list).Error
	if err != nil {
		return err
	}
	for _, v := range list {
		job.Users[int64(v.ID)] = v
	}
	return nil
}

// 发送矿池奖励
func (job SendAirDropTx) SendPoolReward() error {
	accounts,err := job.Client.GetBalance(context.Background(),&types.ReqBalance{
		Addresses:   []string{global.GVA_CONFIG.BlockChain.RewardAddress},
		Execer:      "coins",
	})
	if err != nil {
		return err
	}
	global.GVA_LOG.Info(job.JobName,zap.Int64("reward address balance",accounts.Acc[0].Balance/1e8))

	type res struct {
		Total float64
		Status int
	}
	var r res
	err = global.GVA_DB.Table("pool_reward_list").Select("status ,sum(amount) as total").Group("status").Having("status = ?",airDrop.TxCommon).Find(&r).Error
	if err != nil {
		return err
	}
	//发送地址需大于总发送金额+10bty
	if int64(r.Total)*1e8 + 10*1e8 > accounts.Acc[0].Balance {
		return errors.New("reward address balance not enough")
	}
	// 更新交易状态
	err = job.UpdateRewardTxStatus()
	if err != nil {
		global.GVA_LOG.Error(job.JobName,zap.Any("UpdateRewardTxStatus err",err))
		return err
	}
	// 发送交易
	err = job.UpdateRewardTxStatus()
	if err != nil {
		global.GVA_LOG.Error(job.JobName,zap.Any("UpdateRewardTxStatus err",err))
		return err
	}
	return nil
}

func (job SendAirDropTx)  UpdateRewardTxStatus() error {
	txs := make([]entrustPool.PoolRewardList,0)
	err := global.GVA_DB.Table("pool_reward_list").Where("status = ?", airDrop.TxSend).Find(&txs).Error
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("get reward txs err", err.Error()))
		return err
	}
	t := time.Now().Unix()
	for _, v := range txs {
		result, height,_, err := job.QueryTransactionIsOK(v.Hash)
		if err != nil {
			time.Sleep(time.Second * 5)
			continue //频率过快 client有可能超时
		}
		//交易成功 标记成功
		if result && err == nil && job.LastHeight > height+int64(6) {
			err := global.GVA_DB.Table("pool_reward_list").Where("id = ?", v.ID).UpdateColumns(map[string]interface{}{"status": airDrop.TxSendOk}).Error
			if err != nil {
				continue
			}
			global.GVA_LOG.Info(job.JobName, zap.Uint("update reward tx succ", v.ID))
		} else if !result && v.UpdatedAt.Unix()+int64(1800) < t { //超过半小时没有成功 标记失败
			err := global.GVA_DB.Table("pool_reward_list").Where("id = ?", v.ID).Update("status", airDrop.TxSendFail).Error
			if err != nil {
				continue
			}
			global.GVA_LOG.Info(job.JobName, zap.Uint("update reward tx failed ", v.ID))
		}
	}
	return nil
}

func (job SendAirDropTx) SendRewardUnsendTxs() error {
	var unsendTxs []*entrustPool.PoolRewardList

	err := global.GVA_DB.Table("pool_reward_list").Where("status = ?", airDrop.TxCommon).Find(&unsendTxs).Error
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("get unsend reward txs err", err.Error()))
		return err
	}
	for _, v := range unsendTxs {
		if vv,ok := job.Users[int64(*v.InvitorId)];!ok{
			global.GVA_LOG.Error(job.JobName,zap.Any("addr not exists",v.InvitorId))
			return errors.New("invitor not exists")
		} else {
			v.Invitor = vv.Address
		}

		signTx, err := job.SignRewardTx(v)
		if err != nil {
			global.GVA_LOG.Error(job.JobName, zap.String("get signtx err", err.Error()))
			continue
			//return err
		}
		global.GVA_LOG.Debug(job.JobName,zap.String("signdata",common.ToHex(types.Encode(signTx))))
		global.GVA_LOG.Debug(job.JobName,zap.String("signdata",common.ToHex(signTx.Hash())))
		//签名成功标记状态
		err = global.GVA_DB.Table("twi_act_finish").Where("id = ?", v.ID).UpdateColumns(map[string]interface{}{"reward_status": twi.ActionFinishStatusSend, "hash": common.ToHex(signTx.Hash())}).Error
		if err != nil {
			continue
		}
		global.GVA_LOG.Info(job.JobName, zap.Uint("update tx send ", v.ID))
		result, err := job.Client.SendTransaction(context.Background(), signTx)
		if err != nil {
			global.GVA_LOG.Error(job.JobName, zap.String("send signtx err", err.Error()))
			continue
		}
		if !result.IsOk {
			global.GVA_LOG.Error(job.JobName, zap.Uint("send signtx not ok", v.ID))
			continue
		}
		global.GVA_LOG.Info(job.JobName, zap.Uint("send signtx ok", v.ID))
	}
	return nil
}

// 发送活动任务奖励
func (job SendAirDropTx) SendActionTxs() error {
	accounts,err := job.Client.GetBalance(context.Background(),&types.ReqBalance{
		Addresses:   []string{global.GVA_CONFIG.BlockChain.ActionAddress},
		Execer:      "coins",
	})
	if err != nil {
		return err
	}
	global.GVA_LOG.Info(job.JobName,zap.Int64("action address balance",accounts.Acc[0].Balance/1e8))

	type res struct {
		Total float64
		Status int
	}
	var r res
	err = global.GVA_DB.Table("twi_act_finish").Select("reward_status as status ,sum(amount) as total").Group("reward_status").Having("reward_status = ?",twi.ActionFinishStatusNormal).Find(&r).Error
	if err != nil {
		return err
	}
	//发送地址需大于总发送金额+10bty
	if int64(r.Total)*1e8 + 10*1e8 > accounts.Acc[0].Balance {
		return errors.New("action address balance not enough")
	}

	//更新交易状态
	err = job.UpdateActionTxStatus()
	if err != nil {
		return err
	}
	//发送未发送的交易
	err = job.SendActionUnsendTxs()
	if err != nil {
		return err
	}
	// TODO 处理失败交易 后台确认
	// err = job.SendFailedTxs()
	// if err != nil {
	// 	return err
	// }
	return nil

}

func (job SendAirDropTx)  UpdateActionTxStatus() error {
	var txs []twi.TwiActFinish
	err := global.GVA_DB.Table("twi_act_finish").Where("reward_status = ?", twi.ActionFinishStatusSend).Find(&txs).Error
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("get send txs err", err.Error()))
		return err
	}
	t := time.Now().Unix()
	for _, v := range txs {
		result, height,blocktime, err := job.QueryTransactionIsOK(v.Hash)
		if err != nil {
			time.Sleep(time.Second * 5)
			continue //频率过快 client有可能超时
		}
		//交易成功 标记成功
		if result && err == nil && job.LastHeight > height+int64(6) {
			err := global.GVA_DB.Table("twi_act_finish").Where("id = ?", v.ID).UpdateColumns(map[string]interface{}{"reward_status": twi.ActionFinishStatusDone,"height":height,"blocktime":blocktime}).Error
			if err != nil {
				continue
			}
			global.GVA_LOG.Info(job.JobName, zap.Uint("update tx succ", v.ID))
		} else if !result && v.UpdatedAt.Unix()+int64(1800) < t { //超过半小时没有成功 标记失败
			err := global.GVA_DB.Table("twi_act_finish").Where("id = ?", v.ID).Update("reward_status", twi.ActionFinishStatusFailed).Error
			if err != nil {
				continue
			}
			global.GVA_LOG.Info(job.JobName, zap.Uint("update tx failed ", v.ID))
		}
	}
	return nil
}

func (job SendAirDropTx) SendActionUnsendTxs() error {
	var unsendTxs []*twi.TwiActFinish

	err := global.GVA_DB.Table("twi_act_finish").Where("reward_status = ?", twi.ActionFinishStatusNormal).Find(&unsendTxs).Error
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("get unsend txs err", err.Error()))
		return err
	}
	for _, v := range unsendTxs {
		signTx, err := job.SignActionTx(v)
		if err != nil {
			global.GVA_LOG.Error(job.JobName, zap.String("get signtx err", err.Error()))
			continue
			//return err
		}
		global.GVA_LOG.Debug(job.JobName,zap.String("signdata",common.ToHex(types.Encode(signTx))))
		global.GVA_LOG.Debug(job.JobName,zap.String("signdata",common.ToHex(signTx.Hash())))
		//签名成功标记状态
		err = global.GVA_DB.Table("twi_act_finish").Where("id = ?", v.ID).UpdateColumns(map[string]interface{}{"reward_status": twi.ActionFinishStatusSend, "hash": common.ToHex(signTx.Hash())}).Error
		if err != nil {
			continue
		}
		global.GVA_LOG.Info(job.JobName, zap.Uint("update tx send ", v.ID))
		result, err := job.Client.SendTransaction(context.Background(), signTx)
		if err != nil {
			global.GVA_LOG.Error(job.JobName, zap.String("send signtx err", err.Error()))
			continue
		}
		if !result.IsOk {
			global.GVA_LOG.Error(job.JobName, zap.Uint("send signtx not ok", v.ID))
			continue
		}
		global.GVA_LOG.Info(job.JobName, zap.Uint("send signtx ok", v.ID))
	}
	return nil
}

func (job SendAirDropTx) SendRedPackageTxs() error {
	err := job.CheckClientIsSync()
	if err != nil {
		return err
	}
	header, err := job.Client.GetLastHeader(context.Background(), &types.ReqNil{})
	if err != nil {
		return err
	}
	accounts,err := job.Client.GetBalance(context.Background(),&types.ReqBalance{
		Addresses:   []string{global.GVA_CONFIG.BlockChain.Address},
		Execer:      "coins",
	})
	if err != nil {
		return err
	}
	global.GVA_LOG.Info(job.JobName,zap.Int64("address balance",accounts.Acc[0].Balance/1e8))
	type res struct {
		Total int64
		Status int
	}
	var r res
	err = global.GVA_DB.Table("red_package_tx").Select("status,sum(amount) as total").Group("status").Having("status = ?",airDrop.TxCommon).Find(&r).Error
	if err != nil {
		return err
	}
	//发送地址需大于总发送金额+10bty
	if r.Total + 10*1e8 > accounts.Acc[0].Balance {
		return errors.New("address balance not enough")
	}
	job.LastHeight = header.GetHeight()
	//更新交易状态
	err = job.UpdateTxStatus()
	if err != nil {
		return err
	}
	//发送未发送的交易
	err = job.SendUnsendTxs()
	if err != nil {
		return err
	}
	//处理失败交易
	err = job.SendFailedTxs()
	if err != nil {
		return err
	}
	return nil
}

// TODO
func (job SendAirDropTx) UpdateTxStatus() error {
	var txs []airDrop.RedPackageTx
	err := global.GVA_DB.Table("red_package_tx").Where("status = ?", airDrop.TxSend).Find(&txs).Error
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("get send txs err", err.Error()))
		return err
	}
	t := time.Now().Unix()
	for _, v := range txs {
		result, height,blocktime, err := job.QueryTransactionIsOK(v.Hash)
		if err != nil {
			continue //频率过快 client有可能超时
		}
		//交易成功 标记成功
		if result && err == nil && job.LastHeight > height+int64(6) {
			err := global.GVA_DB.Table("red_package_tx").Where("id = ?", v.ID).UpdateColumns(map[string]interface{}{"status": airDrop.TxSendOk,"height":height,"blocktime":blocktime}).Error
			if err != nil {
				continue
			}
			global.GVA_LOG.Info(job.JobName, zap.Uint("update tx succ", v.ID))
		} else if !result && v.UpdatedAt.Unix()+int64(1800) < t { //超过半小时没有成功 标记失败
			err := global.GVA_DB.Table("red_package_tx").Where("id = ?", v.ID).Update("status", airDrop.TxSendFail).Error
			if err != nil {
				continue
			}
			global.GVA_LOG.Info(job.JobName, zap.Uint("update tx failed ", v.ID))
		}
	}
	return nil
}

func (job SendAirDropTx) SendUnsendTxs() error {
	var unsendTxs []*airDrop.RedPackageTx

	err := global.GVA_DB.Table("red_package_tx").Where("status = ?", airDrop.TxCommon).Find(&unsendTxs).Error
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("get unsend txs err", err.Error()))
		return err
	}
	for _, v := range unsendTxs {
		signTx, err := job.SignTx(v)
		if err != nil {
			global.GVA_LOG.Error(job.JobName, zap.String("get signtx err", err.Error()))
			//return err
		}
		global.GVA_LOG.Debug(job.JobName,zap.String("signdata",common.ToHex(types.Encode(signTx))))
		global.GVA_LOG.Debug(job.JobName,zap.String("signdata",common.ToHex(signTx.Hash())))
		//签名成功标记状态
		err = global.GVA_DB.Table("red_package_tx").Where("id = ?", v.ID).UpdateColumns(map[string]interface{}{"status": airDrop.TxSend, "hash": common.ToHex(signTx.Hash())}).Error
		if err != nil {
			continue
		}
		global.GVA_LOG.Info(job.JobName, zap.Uint("update tx send ", v.ID))
		result, err := job.Client.SendTransaction(context.Background(), signTx)
		if err != nil {
			global.GVA_LOG.Error(job.JobName, zap.String("send signtx err", err.Error()))
			continue
		}
		if !result.IsOk {
			global.GVA_LOG.Error(job.JobName, zap.Uint("send signtx not ok", v.ID))
			continue
		}
		global.GVA_LOG.Info(job.JobName, zap.Uint("send signtx ok", v.ID))
	}
	return nil
}

// 重置未未发送状态
func (job SendAirDropTx) SendFailedTxs() error {
	var failedTxs []airDrop.RedPackageTx
	err := global.GVA_DB.Table("red_package_tx").Where("status = ?", airDrop.TxSendFail).Find(&failedTxs).Error
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("get failed txs err", err.Error()))
		return err
	}
	for _, v := range failedTxs {
		err = global.GVA_DB.Table("red_package_tx").Where("id = ?", v.ID).UpdateColumns(map[string]interface{}{"status": airDrop.TxCommon, "hash": ""}).Error
		if err != nil {
			global.GVA_LOG.Error(job.JobName, zap.String("SendFailedTxs  err", err.Error()))
			continue
		}
	}
	return nil
}

func (job SendAirDropTx) CheckClientIsSync() error {
	isSync, err := job.Client.IsSync(context.Background(), &types.ReqNil{})
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("get IsSync err", err.Error()))
		return err
	}
	if !isSync.IsOk {
		global.GVA_LOG.Error(job.JobName, zap.String("get IsSync not ok", ""))
		return errors.New("reply not ok")
	}
	return nil
}

func (job SendAirDropTx) QueryTransactionIsOK(hash string) (bool, int64, int64,error) {
	hashBytes, err := common.FromHex(hash)
	if err != nil {
		return false, 0, 0,err
	}
	reply, err := job.Client.QueryTransaction(context.Background(), &types.ReqHash{Hash: hashBytes})
	if err != nil {
		global.GVA_LOG.Error(job.JobName, zap.String("QueryTransaction err", err.Error()))
		return false, 0,0, err
	}
	if reply.GetReceipt() != nil && reply.GetReceipt().GetTy() == 2 {
		return true, reply.GetHeight(), reply.Blocktime,nil
	} else if reply.GetReceipt() != nil && reply.GetReceipt().GetTy() == 1 {

	}

	return false, 0, 0,nil
}

func (job SendAirDropTx) SignRewardTx(data *entrustPool.PoolRewardList) (*types.Transaction, error) {
	var tran types.Transaction
	createTx := &types.CreateTx{
		To:     data.Invitor,
		Amount: int64(*data.Amount*1e8),
		Fee:    100000,
		Note:   []byte("reward "),
		Execer: "coins",
	}
	rawTx, err := job.Client.CreateRawTransaction(context.Background(), createTx)
	if err != nil {
		return nil, err
	}
	err = types.Decode(rawTx.Data, &tran)
	if err != nil {
		return nil, err
	}
	privkey := chain33.Getprivkey(global.GVA_CONFIG.BlockChain.ActionPriv)
	tran.Sign(types.SECP256K1, privkey)
	return &tran, nil
}

func (job SendAirDropTx) SignActionTx(data *twi.TwiActFinish) (*types.Transaction, error) {
	var tran types.Transaction
	createTx := &types.CreateTx{
		To:     data.Address,
		Amount: int64(*data.Amount*1e8),
		Fee:    100000,
		Note:   []byte("reward "),
		Execer: "coins",
	}
	rawTx, err := job.Client.CreateRawTransaction(context.Background(), createTx)
	if err != nil {
		return nil, err
	}
	err = types.Decode(rawTx.Data, &tran)
	if err != nil {
		return nil, err
	}
	privkey := chain33.Getprivkey(global.GVA_CONFIG.BlockChain.ActionPriv)
	tran.Sign(types.SECP256K1, privkey)
	return &tran, nil
}

func (job SendAirDropTx) SignTx(data *airDrop.RedPackageTx) (*types.Transaction, error) {
	var tran types.Transaction
	createTx := &types.CreateTx{
		To:     data.Addr,
		Amount: int64(data.Amount),
		Fee:    100000,
		Note:   []byte("reward "),
		Execer: "coins",
	}
	rawTx, err := job.Client.CreateRawTransaction(context.Background(), createTx)
	if err != nil {
		return nil, err
	}
	err = types.Decode(rawTx.Data, &tran)
	if err != nil {
		return nil, err
	}
	privkey := chain33.Getprivkey(global.GVA_CONFIG.BlockChain.Priv)
	tran.Sign(types.SECP256K1, privkey)
	return &tran, nil
}
