package mockengine

import (
	"fmt"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/log"
	"github.com/exchain/go-exchain/exchain"
	"github.com/exchain/go-exchain/exchain/chaindb"
	nebulav1 "github.com/exchain/go-exchain/exchain/protocol/gen/go/nebula/v1"
	"github.com/exchain/go-exchain/exchain/wrapper"
	"math/big"
)

var (
	testFrom = common.HexToAddress("0x00000000000000000000000000000000000000AB")
)

type MockEngine struct {
	log      log.Logger
	dataroot string
	chain    chaindb.ChainDB
}

func (m MockEngine) Start() error {
	return nil
}

func (m MockEngine) fortest(header *nebulav1.BlockHeader) []*nebulav1.Transaction {
	if header.Height == 100 {
		// add a withdrawal tx.
		wtx := nebulav1.WithdrawTransaction{
			User:   common.HexToAddress("0x9910048204119cCA216a2325A3e2fA05eD4977ef").Bytes(),
			Coin:   "ETH",
			Amount: big.NewInt(5e18).Bytes(),
		}
		tx := &nebulav1.Transaction{
			TxType: nebulav1.TxType_WithdrawTx,
			User:   testFrom.Hex(),
			Nonce:  exchain.GetNonce().Bytes(),
			Proxy:  false,
			Tx: &nebulav1.Transaction_WithdrawTx{
				WithdrawTx: &wtx,
			},
			Signature: &nebulav1.Signature{
				R: make([]byte, 32),
				S: make([]byte, 32),
				V: 0,
			},
		}
		return []*nebulav1.Transaction{tx}
	}
	return nil

}

func (m MockEngine) NewPayload(params exchain.PayloadParams) (exchain.ExecutionResult, error) {
	parent, err := m.chain.GetBlockByLabel(chaindb.ExChainBlockLatest)
	if err != nil {
		return exchain.ExecutionResult{}, err
	}
	wParent := wrapper.NewBlkWrapper(parent)
	header := &nebulav1.BlockHeader{
		Height:     parent.Header.Height + 1,
		ParentHash: wParent.Hash().Bytes(),
		Timestamp:  params.Timestamp,
		Proposer:   params.Proposer.Bytes(),
		L1Hash:     params.L1Info.BlockHash.Bytes(),
		L1Height:   params.L1Info.Number,
		AppRoot:    make([]byte, 0),
	}
	txs := &nebulav1.TransactionList{
		Txs: make([]*nebulav1.Transaction, 0),
	}
	if params.Transactions != nil {
		txs.Txs = append(txs.Txs, params.Transactions.Txs...)
	}
	if testTxs := m.fortest(header); testTxs != nil {
		txs.Txs = append(txs.Txs, testTxs...)
	}
	receipts, err := m.ProcessTx(header, txs)
	if err != nil {
		m.log.Error("failed to process tx", "err", err)
		return exchain.ExecutionResult{}, err
	}

	result := exchain.ExecutionResult{
		Payload: &nebulav1.Block{
			Header:       header,
			Transactions: txs,
		},
		Receipts: receipts,
	}
	return result, nil
}

func (m MockEngine) ProcessPayload(block *nebulav1.Block) (exchain.ExecutionResult, error) {
	genesis := block.Header.Height == 0
	if !genesis {
		parent, err := m.chain.GetBlockByLabel(chaindb.ExChainBlockLatest)
		if err != nil {
			return exchain.ExecutionResult{}, err
		}
		if parent.Header.Height+1 != block.Header.Height {
			return exchain.ExecutionResult{}, fmt.Errorf("invalid block height")
		}
	}
	receipts, err := m.ProcessTx(block.Header, block.Transactions)
	if err != nil {
		return exchain.ExecutionResult{}, err
	}

	return exchain.ExecutionResult{
		Payload:  block,
		Receipts: receipts,
	}, nil
}

func NewEngine(dataroot string, log log.Logger, chain chaindb.ChainDB) exchain.Engine {
	return &MockEngine{
		dataroot: dataroot,
		log:      log,
		chain:    chain,
	}
}
