package evmxgo

import (
	"encoding/json"
	"errors"
	"fmt"
	"path/filepath"
	"time"

	"github.com/33cn/chain33/common/address"
	"github.com/33cn/chain33/common/db"
	types33 "github.com/33cn/chain33/types"
	evmxgotypes "github.com/33cn/plugin/plugin/dapp/evmxgo/types"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cast"
	"gitlab.33.cn/link33/sidecar-client-chain33/client/rpc"
	"gitlab.33.cn/link33/sidecar-client-chain33/config"
	"gitlab.33.cn/link33/sidecar-client-chain33/types"
	"gitlab.33.cn/link33/sidecar-client-chain33/util"
	"gitlab.33.cn/link33/sidecar/model/pb"
	"gitlab.33.cn/link33/sidecar/pkg/log"
	"gitlab.33.cn/link33/sidecar/pkg/plugins"
	"go.uber.org/atomic"
)

// 监听已铸币成功事件：异步完成
// 铸币操作，打给用户地址【操作只能在用户公链一侧发起，被铸造入币的地址，由发起操作时带入，取之范围为：from、或者Recipient。】。
// 事件：铸币成功 <=> lock，
// 构造交易发给公链

// 监听已销毁代币事件：异步完成
// 销毁操作，销毁的地址值为：from地址或Recipient地址【默认是签名地址，实际是from地址，需要核对地址是否一致，能否销毁】。操作发起，两种情况：1、在公链发起，销毁；2、在联盟链发起，销毁from地址。
// 构造交易发给公链，

var (
	_          plugins.Client = (*Evmxgo)(nil)
	PluginName                = evmxgotypes.EvmxgoX
	ID                        = evmxgotypes.EvmxgoX
	execer                    = evmxgotypes.EvmxgoX
	logger     *logrus.Entry
	// logger     = log.NewWithModuleLevel("chain33-client-"+PluginName, "trace")
)

type Evmxgo struct {
	eventC  chan *pb.IBTP // 发送给sidecar
	chainID int32
	fromID  string
	toID    string
	ticker  *time.Ticker
	done    chan bool
	client  *rpc.Client
	pk      string         // 签名，仅仅是和当前链挂钩的私钥。
	height  *atomic.Int64  // 已经解析的高度。1、初始化时设置； 2、服务每次重启后设置
	db      *util.KVDBList // 记录已完成的hashID、高度等信息。
	fee     int64
}

func (e *Evmxgo) incHeight() int64 {
	h := e.height.Inc()
	value := fmt.Sprintf("%d", h)
	err := e.db.Set([]byte("latest-height"), []byte(value))
	if err != nil {
		logger.Error("call incHeight set latest-height", "error", err)
	}
	return h
}

func (e *Evmxgo) Start() error {
	go e.loop()
	logger.Info("Evmxgo  started")
	return nil
}

func (e *Evmxgo) Stop() error {
	e.done <- true
	e.ticker.Stop()
	e.db.Close()
	return nil
}

func (e *Evmxgo) Name() string {
	// TODO implement me
	return ID
}

func (e *Evmxgo) Type() string {
	return ID
}

func (e *Evmxgo) ID() string {
	return e.fromID
}

// configPath 完整路径
func (e *Evmxgo) Initialize(configPath string, _ string, _ []byte) error {
	chain33Config, err := config.UnmarshalConfig(configPath)
	if err != nil {
		return fmt.Errorf("unmarshal config for plugin :%w", err)
	}
	err = log.Initialize(
		log.WithReportCaller(chain33Config.Log.ReportCaller == true),
		log.WithPersist(true),
		log.WithFilePath(filepath.Join(filepath.Dir(configPath), "logs")),
		log.WithFileName("chain33-client-"+PluginName+".log"),
		log.WithMaxSize(2*1024*1024),
		log.WithMaxAge(24*time.Hour),
		log.WithRotationTime(24*time.Hour),
	)
	if err != nil {
		return fmt.Errorf("log initialize: %w", err)
	}
	logger = log.NewWithModuleLevel("chain33-client-"+PluginName, chain33Config.Log.Level)
	// 初始化e.client工作
	e.eventC = make(chan *pb.IBTP, 10)
	e.done = make(chan bool)
	e.ticker = time.NewTicker(2 * time.Second)
	logger.Info("print chain33Config", "chain33Config", util.Sprint(chain33Config))
	logger.Info("print privateKey", "privateKey", chain33Config.Chain33.PrivateKey)
	e.pk = chain33Config.Chain33.PrivateKey
	e.chainID = chain33Config.Chain33.ChainID
	e.fromID = chain33Config.Router.FromChain
	e.toID = chain33Config.Router.ToChain
	e.fee = chain33Config.Chain33.Fee

	// 初始化chain33 client rpc
	client, err := rpc.NewClient("", chain33Config.Chain33.Addr)
	if err != nil {
		return err
	}
	e.client = client
	// 加载高度，从本地磁盘加载。
	dbConfig := chain33Config.DB
	// blockStoreDB := dbm.NewDB("blockchain", "leveldb", dir, 100)
	d := db.NewDB(dbConfig.Name, dbConfig.Driver, dbConfig.DbPath, dbConfig.DbCache)
	e.db = util.NewKVDBList(d)
	// 加载高度，是否可以存在区块链上
	data, err := e.db.Get([]byte("latest-height"))
	logger.Info("latest height", "height", string(data))
	if err != nil {
		logger.Error("latest_height", "err", err)
		e.height = atomic.NewInt64(0)
	} else {
		e.height = atomic.NewInt64(cast.ToInt64(string(data)))
	}

	if e.height.Load() < chain33Config.Chain33.StartHeight {
		e.height = atomic.NewInt64(chain33Config.Chain33.StartHeight)
	}
	var startHeight int64 = 0
	data1, err1 := e.db.Get([]byte("start-height"))
	if err1 == nil {
		startHeight = cast.ToInt64(string(data1))
	}
	_ = e.db.Set([]byte("start-height"), []byte(fmt.Sprintf("%d", chain33Config.Chain33.StartHeight)))
	if startHeight != chain33Config.Chain33.StartHeight {
		e.height = atomic.NewInt64(chain33Config.Chain33.StartHeight)
	}
	logger.Info("startHeight ", "startHeight", startHeight)
	logger.Info("latest height", "height", e.height.Load())
	// 启动定时器，检查是否有遗漏的数据。

	// 检验已经完成的数据。

	return nil
}

func (e *Evmxgo) GetIBTP() chan *pb.IBTP {
	return e.eventC
}

// pollAppChain event from Evmxgo
func (e *Evmxgo) pollAppChain() chan *types.Event {
	// TODO 定时获取关注的交易、日志、区块等，然后解析到关注的数据内容。
	// 获取用户日志，记录对应的日志（hash）放入对应的的数据库里。或者记录本地数据库，做一一的绑定。
	// 获取receipt log日志，解析，构造交易，发送交易，区块高度记录，记录已发生的交易。
	// e.client.SendTransaction() //构造交易，签名，发送交易
	// e.client.QueryLastHeader() //公链用的到，
	// e.client.QueryBlockInfo()  //查询区块，获取交易，并解析
	// e.client.QueryIsSync()     //验证区块服务状态
	// e.client.QueryTransaction()//查询指定交易
	// 1:lock <=> mint; 2 unlock <=> burn 一一绑定，通过双方的hash。把绑定关系，写入到[双方]区块链中。
	// 公链事件：lock: topic + execer + transfer; unlock: topic + execer + withdraw。联盟链：mint: execer + mintMap; burn: execer + burnMap
	// 公链：当前高度与最新高度相差6～15个块；联盟链：获取最新的块即可。
	// 提币必须成功，绑定。公链是主链；联盟链是映射链。
	// 用户在公链一侧发起锁定、解锁（销毁）操作。
	// 用户在联盟链一侧解锁（销毁）操作。
	// 限制：1、同一个用户，不允许连续提2笔交易。2、锁定操作只能在公链一侧发起。
	// 侧链用户，只要有代币，就可以销毁，同时在公链一侧做解锁操作，不管是否有解锁。（同一账户【地址】体系，或者附带用户的公链地址）
	ch := make(chan *types.Event, 1)
	go func() {
		for {
			select {
			case <-e.ticker.C:
				header, err := e.client.QueryLastHeader()
				if err != nil {
					logger.Error("call QueryLastHeader ", "error", err)
					time.Sleep(2 * time.Second)
				} else {
					logger.Info("GetLastHeader", "height", header.Height)
					lastHeight := header.Height
					height := e.height.Load()
					if lastHeight <= height {
						time.Sleep(2 * time.Second)
					} else {
						height1 := height + 1
						blockInfo, err := e.client.QueryBlockInfo(height1, height1, true)
						if err != nil {
							logger.Error("call QueryBlockInfo", "error", err)
						} else {
							for _, item := range blockInfo.Items {
								height1 := item.Block.Height
								logger.Info("deal height block begin-----------------------", "height", height1)
								for i, tx := range item.Block.Txs {
									if tx.Execer == execer {
										switch item.Receipts[i].Ty {
										case types33.ExecOk: // OK
											for _, log := range item.Receipts[i].Logs {
												if log.Ty == evmxgotypes.TyLogEvmxgoMintMap || log.Ty == evmxgotypes.TyLogEvmxgoBurnMap {
													logger.Info("deal tx", "hash", tx.Hash)
													logger.Info("printf tx", "tx", util.FormatJSON(tx))
												}
												if log.Ty == evmxgotypes.TyLogEvmxgoMintMap {
													type (
														MintMap struct {
															Symbol      string `json:"symbol,omitempty"`
															Amount      string `json:"amount,omitempty"`
															BridgeToken string `json:"bridgeToken,omitempty"`
															Recipient   string `json:"recipient,omitempty"`
															// Extra       []byte `json:"extra,omitempty"` TODO?
														}
														Action struct {
															MintMap *MintMap `json:"mintMap,omitempty"`
															Ty      int32    `json:"Ty,omitempty"`
														}
													)
													action := &Action{}
													err := json.Unmarshal(tx.Payload, &action)
													// action := &evmxgotypes.EvmxgoAction{}
													// err := types33.Decode(tx.Payload, action)
													if err != nil {
														logger.Error("MintMap Unmarshal tx.payload to action",
															"height:", item.Block.Height,
															"tx:", util.FormatJSON(tx),
															"error", err,
														)
														continue
													}
													mintEvent := action.MintMap
													// extra, _ := util.FromHex(mintEvent.Extra)
													event := &types.Event{
														TxID:        tx.Hash,
														BlockNumber: uint64(item.Block.Height),
														Contract: &types.Contract{
															ChainID:    e.fromID,
															ExecName:   ID,
															Address:    address.ExecAddress(ID), // 合约地址
															CallMethod: evmxgotypes.NameMintMapAction,
														},
														Payload: &types.MapAssetInfo{
															EventType:   types.EventType_Mint,
															MintAddress: mintEvent.Recipient,
															MintAmount:  cast.ToInt64(mintEvent.Amount),
															Symbol:      types.Symbol(mintEvent.Symbol),
															Height:      height1,
															Nonce:       tx.Nonce,
															// Extra:       mintEvent.Extra,
														},
													}
													srcEvent := &types.Event{}
													//err = types33.Decode(mintEvent.Extra, srcEvent)
													//if err != nil {
													//	logger.Error("types33.Decode(mintEvent.Extra, &srcEvent)", "error", err)
													//	continue
													//}
													logger.Info("mintmap succeed：printf mintEvent", "mintEvent", util.FormatJSON(action))
													e.saveMint(event)
													e.updateLockMint(srcEvent, event)
													// ch <- event//TODO
													break
												}
												if log.Ty == evmxgotypes.TyLogEvmxgoBurnMap {
													type (
														BurnMap struct {
															Symbol string `json:"symbol,omitempty"`
															Amount string `json:"amount,omitempty"`
															Extra  []byte `json:"extra,omitempty"`
														}
														Action struct {
															BurnMap *BurnMap `json:"burnMap,omitempty"`
															Ty      int32    `json:"Ty,omitempty"`
														}
													)
													action := &Action{}
													err := json.Unmarshal(tx.Payload, &action)
													if err != nil {
														logger.Error("BurnMap Unmarshal tx.payload to action",
															"height:", item.Block.Height,
															"tx:", util.FormatJSON(tx),
															"error", err,
														)
														continue
													}
													burnEvent := action.BurnMap
													logger.Info("printf burnEvent", "burnEvent", util.FormatJSON(action))
													// extra, _ := util.FromHex(burnEvent.Extra)
													// 已销毁
													event := &types.Event{
														TxID:        tx.Hash,
														BlockNumber: uint64(item.Block.Height),
														Contract: &types.Contract{
															ChainID:    cast.ToString(e.chainID),
															ExecName:   ID,
															Address:    address.ExecAddress(ID), // 合约地址
															CallMethod: evmxgotypes.NameBurnMapAction,
														},
														Payload: &types.MapAssetInfo{
															EventType:   types.EventType_Burn,
															BurnAddress: tx.From, // 用户地址
															BurnAmount:  cast.ToInt64(burnEvent.Amount),
															Symbol:      types.Symbol(burnEvent.Symbol),
															Height:      height1,
															Nonce:       tx.Nonce,
															// Extra:       extra, //TODO
														},
													}
													e.saveBurn(event)
													e.saveUndoneEvent(event)
													ch <- event
													break
												}
											}

										case types33.ExecPack: // 打包：交易
											// TODO 执行失败，但是被打包的交易需要过滤出来。决策是否重新发送。如：交易费问题、余额不足等等。
											logger.Error("exec pack!",
												"height:", item.Block.Height,
												"tx:", util.FormatJSON(tx),
											)
											// 指定交易更新

										case types33.ExecErr: // Error
											logger.Error("exec error ",
												"height:", item.Block.Height,
												"tx:", util.FormatJSON(tx),
											)
										default:
											logger.Error("exec type error!",
												"type", item.Receipts[i].Ty,
												"tx:", util.FormatJSON(tx),
												"height:", item.Block.Height,
											)
										}
									}
								}
								logger.Info("deal height block end-----------------------", "height", height1)
								e.incHeight()
							}
						}
					}
				}

			case <-e.done:
				logger.Info("Stop long pollAppChain")
				break
			}
		}
	}()
	return ch
}

// 保存event: key = prefix + chainID + txid ; value = event
func (e *Evmxgo) saveXEvent(prefix string, event *types.Event) {
	// key: prefix + chainID + txID
	key := fmt.Sprintf("%s-%s-%s", prefix, event.Contract.ChainID, event.TxID)
	data := types33.Encode(event)
	_ = e.db.Set([]byte(key), data)
	e.saveHeightEvent(event)
}

// 在联盟链一侧，用户发起销毁：1、销毁部分。2、销毁全部
func (e *Evmxgo) saveBurn(event *types.Event) {
	e.saveXEvent("burn", event)
}

func (e *Evmxgo) saveMint(event *types.Event) {
	e.saveXEvent("mint", event)
}

// 在公链一侧，用户发起铸币。
func (e *Evmxgo) saveLock(event *types.Event) {
	e.saveXEvent("lock", event)
}

// 指定高度的交易: key = "height-" + chainID + blocknumber + txid; value = txid
func (e *Evmxgo) saveHeightEvent(event *types.Event) {
	key := fmt.Sprintf("%d-%s-%s", event.BlockNumber, event.Contract.ChainID, event.TxID)
	_ = e.db.Set([]byte(key), []byte(event.TxID))
}

// 未完成绑定的event: key = "undone-" + txid; value = txID
func (e *Evmxgo) saveUndoneEvent(event *types.Event) {
	key := fmt.Sprintf("undone-%s", event.TxID)
	_ = e.db.Set([]byte(key), []byte(event.TxID))
}

// 完成绑定的event: key = "done-" + txid; value = txID1
func (e *Evmxgo) saveDoneEvent(event *types.Event, event1 *types.Event) {
	key := fmt.Sprintf("done-%s", event.TxID)
	_ = e.db.Set([]byte(key), []byte(event1.TxID))
}

// 已完成、指定高度的event绑定： key = "height-done" + blocknumber +  txid; value = txID1
func (e *Evmxgo) saveHeightDoneEvent(event *types.Event, event1 *types.Event) {
	key := fmt.Sprintf("height-done-%d-%s", event.BlockNumber, event.TxID)
	_ = e.db.Set([]byte(key), []byte(event1.TxID))
}

func (e *Evmxgo) deleteUndoneEvent(txID string) {
	_ = e.db.Delete([]byte(fmt.Sprintf("undone-%s", txID)))
}

// 需要完成双向握手
// burn => unlock => ack burn
func (e *Evmxgo) updateBurnUnlock(burnEvent, unlockEvent *types.Event) {
	e.deleteUndoneEvent(burnEvent.TxID)
	e.deleteUndoneEvent(unlockEvent.TxID)
	e.saveDoneEvent(burnEvent, unlockEvent)
	e.saveHeightDoneEvent(burnEvent, unlockEvent)
}

// lock => mint => ack lock
func (e *Evmxgo) updateLockMint(lockEvent, mintEvent *types.Event) {
	e.deleteUndoneEvent(lockEvent.TxID)
	e.deleteUndoneEvent(mintEvent.TxID)
	e.saveDoneEvent(lockEvent, mintEvent)
	e.saveHeightDoneEvent(lockEvent, mintEvent)
}

// unlock => burn => ack unlock
func (e *Evmxgo) updateUnlockBurn(unlockEvent, burnEvent *types.Event) {
	panic("not implement me")
	e.deleteUndoneEvent(unlockEvent.TxID)
	e.deleteUndoneEvent(burnEvent.TxID)
	e.saveDoneEvent(unlockEvent, burnEvent)
	e.saveHeightDoneEvent(unlockEvent, burnEvent)
}

// mint => lock => ack mint
func (e *Evmxgo) updateMintLock(mintEvent, lockEvent *types.Event) {
	panic("not implement me")
}

func (e *Evmxgo) loop() {
	ch := e.pollAppChain()
	for event := range ch {
		logger.Debug("loop method recv", "event", util.FormatJSON(event))
		if e.isFocus(event) {
			payload := pb.Payload{
				Encrypted: false, // TODO
				Content:   types33.Encode(event),
			}
			data, _ := payload.Marshal()
			tmp := time.Now().UnixNano()
			ibtp := &pb.IBTP{
				From:      e.fromID,
				To:        e.toID,
				Nonce:     uint64(event.Payload.Nonce), // TODO 唯一标识。随机nonce
				Type:      pb.IBTP_INTERCHAIN,
				Timestamp: tmp,
				Payload:   data,
				Version:   types.Version,
			}
			e.eventC <- ibtp
		} else {
			logger.Info("is not focus event", "event", util.FormatJSON(event))
		}
	}
}

// 用户在公链发起lock -> 联盟链mint -> 回执lock ack，
// 用户在公链发起unlock -> 联盟链burn -> 回执 ack unlock
// 如果不携带extra，需要查询，对应的hash是否存在，如果携带，也需要查询。
// 用户在联盟链发起burn-> 公链unlock -> 回执 ack burn,
// 查询到的证明交易已经执行过了。
// paracorss解析note字段,与 extra字段做双向绑定。

// 交易延迟执行，
// paracorss: 转账：主链执行完成，侧链（平行链）ack确认，否则回滚。
// paracorss: 提币：主链并未执行，侧链（平行链）ack确认，通过索引查询，在最新高度重新执行。状态复制。
// 这个就是延迟交易：1、一个是设置固定高度，一定时间自动出发，2、外界触发。
// 模拟链上数据，数据存在在平行链上，做关联数据，

// 规定：
// 用户在公链发起铸币。逻辑决定。
// 销毁在联盟链一侧发起。evmxgo合约决定。

// 每个插件必持久化数据，否则，很容易丢失的。
func (e *Evmxgo) SubmitIBTP(ibtp *pb.IBTP) (*pb.SubmitIBTPResponse, error) {
	logger.Debug("SubmitIBTP recv ibtp", "ibtp", util.FormatJSON(ibtp))
	switch ibtp.Type {
	case pb.IBTP_INTERCHAIN:
		event := &types.Event{}
		payload := &pb.Payload{}
		err := payload.Unmarshal(ibtp.Payload)
		if err != nil {
			return nil, err
		}
		err = types33.Decode(payload.Content, event)
		if err != nil {
			logger.Error("decode error", err)
			return nil, err
		}
		if event.Payload.EventType == types.EventType_Lock {
			d, _ := types33.PBToJSON(event)
			logger.Debug("SubmitIBTP recv event", "event", util.FormatJSON(string(d)))
			// TODO 校验是否已经存在！
			e.saveLock(event)
			tx, err := e.client.CreateTransaction(execer, "MintMap", &evmxgotypes.EvmxgoMintMap{
				Symbol:    event.Payload.Symbol,
				Amount:    event.Payload.LockAmount,
				Recipient: event.Payload.LockAddress,
				Extra:     payload.Content, // TODO 附加已经完成的事件
			})
			if err != nil {
				logger.Error("e.client.CreateTransaction", "error", err)
				return nil, err
			}

			tx.Nonce = int64(ibtp.Nonce)
			res, err := e.client.SendTransactionSync(e.pk, tx) // TODO why use async?
			if err != nil {
				logger.Error("e.client.SendTransactionSync(e.pk,tx)", "error", err)
				return nil, err
			}
			ibtpRes := &pb.SubmitIBTPResponse{}
			ibtpRes.Status = true
			//_ = e.db.Set(tx.Hash(), ibtp.Payload)       // TODO
			//_ = e.db.Set([]byte(event.TxID), tx.Hash()) // TODO
			e.saveUndoneEvent(event)
			ibtpRes.Result = ibtp
			ibtpRes.Message = res.Message // TODO 返回一个ack,主链确认。
			logger.Info("SendTransactionSync tx ok", "hash", res.Message)
			return ibtpRes, nil
		}
		if event.Payload.EventType == types.EventType_UnLock {
		}

	// case pb.IBTP_RECEIPT_SUCCESS:
	// case pb.IBTP_RECEIPT_FAILURE:
	// case pb.IBTP_ROLLBACK:
	// case pb.IBTP_RECEIPT_ROLLBACK:
	default:
		logger.Error("ibtp.Type error", "type", ibtp.Type)
	}
	// 返回结果是一个状态。并不是回执。需要保证对应的状态。或者请求持久化。
	return nil, errors.New("ibtp.Type error")
}

func (e *Evmxgo) isFocus(event *types.Event) bool {
	ey := event.Payload.EventType
	extra := event.Payload.Extra
	switch {
	case ey == types.EventType_Mint && len(extra) > 0:
		return true
	case ey == types.EventType_Burn && len(extra) == 0:
		return true
	case ey == types.EventType_Lock:
		return true
	case ey == types.EventType_UnLock:
		return true

	default:
		return false
	}
}
