package wrapper

import (
	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	nebulav1 "github.com/exchain/go-exchain/exchain/protocol/gen/go/nebula/v1"
	"github.com/golang/protobuf/proto"
	"math/big"
)

type TxWrapper struct {
	tx   *nebulav1.Transaction
	hash common.Hash
}

func NewTxWrapper(tx *nebulav1.Transaction) *TxWrapper {
	return &TxWrapper{tx: tx}
}

func (t *TxWrapper) Hash() common.Hash {
	if t.hash == (common.Hash{}) {
		t.hash = t.calcHash()
	}
	return t.hash
}

func (t *TxWrapper) Clone() *nebulav1.Transaction {
	return proto.Clone(t.tx).(*nebulav1.Transaction)
}

func (t *TxWrapper) calcHash() common.Hash {
	ntx := t.Clone()
	ntx.Signature = nil

	data, _ := proto.Marshal(ntx)
	return crypto.Keccak256Hash(data)

}

func withdrawalHash(user common.Address, coin string, amount *big.Int, txHash common.Hash) common.Hash {
	uint256Ty, _ := abi.NewType("uint256", "", nil)
	bytes32Ty, _ := abi.NewType("bytes32", "", nil)
	addressTy, _ := abi.NewType("address", "", nil)
	bytesTy, _ := abi.NewType("bytes", "", nil)
	arguments := abi.Arguments{
		{
			Type: addressTy,
		},
		{
			Type: bytesTy,
		},
		{
			Type: uint256Ty,
		},
		{
			Type: bytes32Ty,
		},
	}
	data, err := arguments.Pack(user, []byte(coin), amount, txHash)
	if err != nil {
		return common.Hash{}
	}
	return crypto.Keccak256Hash(data)
}

func (t *TxWrapper) WithdrawalHash() common.Hash {
	if t.tx.TxType != nebulav1.TxType_WithdrawTx {
		return common.Hash{}
	}
	wtx := t.tx.GetWithdrawTx()
	if wtx == nil {
		return common.Hash{}
	}
	user := common.BytesToAddress(wtx.User)
	coin := string(wtx.Coin)
	amount := new(big.Int).SetBytes(wtx.Amount)
	txHash := t.Hash()
	return withdrawalHash(user, coin, amount, txHash)
}

func (t *TxWrapper) Bytes() ([]byte, error) {
	return proto.Marshal(t.tx)
}

func (t *TxWrapper) IsProtocolTx() bool {
	return t.tx.TxType == nebulav1.TxType_ProtocolTx
}
