package ethtxs

import (
	"context"
	"crypto/ecdsa"
	"errors"
	"math/big"

	"github.com/ethereum/go-ethereum/accounts/abi/bind"

	"github.com/33cn/plugin/plugin/dapp/x2Ethereum/ebrelayer/ethcontract/generated"
	"github.com/33cn/plugin/plugin/dapp/x2Ethereum/ebrelayer/events"
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
)

type NewProphecyClaimPara struct {
	ClaimType     uint8
	Chain33Sender []byte
	TokenAddr     common.Address
	EthReceiver   common.Address
	Symbol        string
	Amount        *big.Int
	Txhash        []byte
}

func CreateBridgeToken(symbol string, backend bind.ContractBackend, para *OperatorInfo, x2EthDeployInfo *X2EthDeployInfo, x2EthContracts *X2EthContracts) (string, error) {
	if nil == para {
		return "", errors.New("No operator private key configured")
	}
	//订阅事件
	eventName := "LogNewBridgeToken"
	bridgeBankABI := LoadABI(BridgeBankABI)
	logNewBridgeTokenSig := bridgeBankABI.Events[eventName].ID().Hex()
	query := ethereum.FilterQuery{
		Addresses: []common.Address{x2EthDeployInfo.BridgeBank.Address},
	}
	// We will check logs for new events
	logs := make(chan types.Log)
	// Filter by contract and event, write results to logs
	client := backend.(*ethclient.Client)
	sub, err := client.SubscribeFilterLogs(context.Background(), query, logs)
	if nil != err {
		txslog.Error("CreateBrigeToken", "failed to SubscribeFilterLogs", err.Error())
		return "", err
	}

	var prepareDone bool

	defer func() {
		if err != nil && prepareDone {
			_, _ = revokeNonce(para.Address)
		}
	}()

	//创建token
	auth, err := PrepareAuth(backend, para.PrivateKey, para.Address)
	if nil != err {
		return "", err
	}

	prepareDone = true

	tx, err := x2EthContracts.BridgeBank.BridgeBankTransactor.CreateNewBridgeToken(auth, symbol)
	if nil != err {
		return "", err
	}
	err = waitEthTxFinished(client, tx.Hash(), "CreateBridgeToken")
	if nil != err {
		return "", err
	}

	logEvent := &events.LogNewBridgeToken{}
	select {
	// Handle any errors
	case err := <-sub.Err():
		return "", err
	// vLog is raw event data
	case vLog := <-logs:
		// Check if the event is a 'LogLock' event
		if vLog.Topics[0].Hex() == logNewBridgeTokenSig {
			txslog.Debug("CreateBrigeToken", "Witnessed new event", eventName, "Block number", vLog.BlockNumber)

			err = bridgeBankABI.Unpack(logEvent, eventName, vLog.Data)
			if nil != err {
				return "", err
			}
			if symbol != logEvent.Symbol {
				txslog.Error("CreateBrigeToken", "symbol", symbol, "logEvent.Symbol", logEvent.Symbol)
			}
			txslog.Info("CreateBrigeToken", "Witnessed new event", eventName, "Block number", vLog.BlockNumber, "token address", logEvent.Token.String())
			break
		}
	}
	return logEvent.Token.String(), nil
}

func CreateERC20Token(symbol string, backend bind.ContractBackend, para *OperatorInfo, x2EthDeployInfo *X2EthDeployInfo, x2EthContracts *X2EthContracts) (string, error) {
	if nil == para {
		return "", errors.New("No operator private key configured")
	}

	var prepareDone bool
	var err error

	defer func() {
		if err != nil && prepareDone {
			_, _ = revokeNonce(para.Address)
		}
	}()

	auth, err := PrepareAuth(backend, para.PrivateKey, para.Address)
	if nil != err {
		return "", err
	}

	prepareDone = true

	tokenAddr, tx, _, err := generated.DeployBridgeToken(auth, backend, symbol)
	if nil != err {
		return "", err
	}

	err = waitEthTxFinished(backend.(*ethclient.Client), tx.Hash(), "CreateERC20Token")
	if nil != err {
		return "", err
	}

	return tokenAddr.String(), nil
}

func MintERC20Token(tokenAddr, ownerAddr string, amount *big.Int, backend bind.ContractBackend, para *OperatorInfo) (string, error) {
	if nil == para {
		return "", errors.New("No operator private key configured")
	}

	var prepareDone bool
	var err error

	defer func() {
		if err != nil && prepareDone {
			_, _ = revokeNonce(para.Address)
		}
	}()

	operatorAuth, err := PrepareAuth(backend, para.PrivateKey, para.Address)
	if nil != err {
		return "", err
	}

	prepareDone = true

	erc20TokenInstance, err := generated.NewBridgeToken(common.HexToAddress(tokenAddr), backend)
	if nil != err {
		return "", err
	}
	tx, err := erc20TokenInstance.Mint(operatorAuth, common.HexToAddress(ownerAddr), amount)
	if nil != err {
		return "", err
	}

	err = waitEthTxFinished(backend.(*ethclient.Client), tx.Hash(), "MintERC20Token")
	if nil != err {
		return "", err
	}

	return tx.Hash().String(), nil
}

func ApproveAllowance(ownerPrivateKeyStr, tokenAddr string, bridgeBank common.Address, amount *big.Int, backend bind.ContractBackend) (string, error) {
	ownerPrivateKey, err := crypto.ToECDSA(common.FromHex(ownerPrivateKeyStr))
	if nil != err {
		return "", err
	}
	ownerAddr := crypto.PubkeyToAddress(ownerPrivateKey.PublicKey)

	var prepareDone bool

	defer func() {
		if err != nil && prepareDone {
			_, _ = revokeNonce(ownerAddr)
		}
	}()

	auth, err := PrepareAuth(backend, ownerPrivateKey, ownerAddr)
	if nil != err {
		return "", err
	}

	prepareDone = true

	erc20TokenInstance, err := generated.NewBridgeToken(common.HexToAddress(tokenAddr), backend)
	if nil != err {
		return "", err
	}

	tx, err := erc20TokenInstance.Approve(auth, bridgeBank, amount)
	if nil != err {
		return "", err
	}

	err = waitEthTxFinished(backend.(*ethclient.Client), tx.Hash(), "ApproveAllowance")
	if nil != err {
		return "", err
	}

	return tx.Hash().String(), nil
}

func Burn(ownerPrivateKeyStr, tokenAddrstr, chain33Receiver string, bridgeBank common.Address, amount *big.Int, bridgeBankIns *generated.BridgeBank, backend bind.ContractBackend) (string, error) {
	ownerPrivateKey, err := crypto.ToECDSA(common.FromHex(ownerPrivateKeyStr))
	if nil != err {
		return "", err
	}
	ownerAddr := crypto.PubkeyToAddress(ownerPrivateKey.PublicKey)
	var prepareDone bool

	defer func() {
		if err != nil && prepareDone {
			_, _ = revokeNonce(ownerAddr)
		}
	}()
	auth, err := PrepareAuth(backend, ownerPrivateKey, ownerAddr)
	if nil != err {
		return "", err
	}

	prepareDone = true

	tokenAddr := common.HexToAddress(tokenAddrstr)
	tokenInstance, err := generated.NewBridgeToken(tokenAddr, backend)
	if nil != err {
		return "", err
	}
	//chain33bank 是bridgeBank的基类，所以使用bridgeBank的地址
	tx, err := tokenInstance.Approve(auth, bridgeBank, amount)
	if nil != err {
		return "", err
	}
	client := backend.(*ethclient.Client)
	err = waitEthTxFinished(client, tx.Hash(), "Approve")
	if nil != err {
		return "", err
	}
	txslog.Info("Burn", "Approve tx with hash", tx.Hash().String())

	prepareDone = false

	auth, err = PrepareAuth(backend, ownerPrivateKey, ownerAddr)
	if nil != err {
		return "", err
	}

	prepareDone = true

	tx, err = bridgeBankIns.BurnBridgeTokens(auth, []byte(chain33Receiver), tokenAddr, amount)
	if nil != err {
		return "", err
	}
	err = waitEthTxFinished(client, tx.Hash(), "Burn")
	if nil != err {
		return "", err
	}

	return tx.Hash().String(), nil
}

func BurnAsync(ownerPrivateKeyStr, tokenAddrstr, chain33Receiver string, bridgeBank common.Address, amount *big.Int, bridgeBankIns *generated.BridgeBank, backend bind.ContractBackend) (string, error) {
	ownerPrivateKey, err := crypto.ToECDSA(common.FromHex(ownerPrivateKeyStr))
	if nil != err {
		return "", err
	}
	ownerAddr := crypto.PubkeyToAddress(ownerPrivateKey.PublicKey)

	var prepareDone bool

	defer func() {
		if err != nil && prepareDone {
			_, _ = revokeNonce(ownerAddr)
		}
	}()
	auth, err := PrepareAuth(backend, ownerPrivateKey, ownerAddr)
	if nil != err {
		return "", err
	}

	prepareDone = true

	tokenAddr := common.HexToAddress(tokenAddrstr)
	tx, err := bridgeBankIns.BurnBridgeTokens(auth, []byte(chain33Receiver), tokenAddr, amount)
	if nil != err {
		return "", err
	}

	return tx.Hash().String(), nil
}

func TransferToken(tokenAddr, fromPrivateKeyStr, toAddr string, amount *big.Int, backend bind.ContractBackend) (string, error) {
	tokenInstance, err := generated.NewBridgeToken(common.HexToAddress(tokenAddr), backend)
	if nil != err {
		return "", err
	}

	var prepareDone bool

	fromPrivateKey, err := crypto.ToECDSA(common.FromHex(fromPrivateKeyStr))
	if nil != err {
		return "", err
	}
	fromAddr := crypto.PubkeyToAddress(fromPrivateKey.PublicKey)

	defer func() {
		if err != nil && prepareDone {
			_, _ = revokeNonce(fromAddr)
		}
	}()

	auth, err := PrepareAuth(backend, fromPrivateKey, fromAddr)
	if nil != err {
		return "", err
	}

	prepareDone = true

	tx, err := tokenInstance.Transfer(auth, common.HexToAddress(toAddr), amount)
	if nil != err {
		return "", err
	}

	err = waitEthTxFinished(backend.(*ethclient.Client), tx.Hash(), "TransferFromToken")
	if nil != err {
		return "", err
	}
	return tx.Hash().String(), nil
}

func LockEthErc20Asset(ownerPrivateKeyStr, tokenAddrStr, chain33Receiver string, amount *big.Int, backend bind.ContractBackend, bridgeBank *generated.BridgeBank, bridgeBankAddr common.Address) (string, error) {
	var prepareDone bool
	txslog.Info("LockEthErc20Asset", "ownerPrivateKeyStr", ownerPrivateKeyStr, "tokenAddrStr", tokenAddrStr, "chain33Receiver", chain33Receiver, "amount", amount.String())
	ownerPrivateKey, err := crypto.ToECDSA(common.FromHex(ownerPrivateKeyStr))
	if nil != err {
		return "", err
	}
	ownerAddr := crypto.PubkeyToAddress(ownerPrivateKey.PublicKey)

	defer func() {
		if err != nil && prepareDone {
			_, _ = revokeNonce(ownerAddr)
		}
	}()

	//ETH转账，空地址，且设置value
	var tokenAddr common.Address
	if "" != tokenAddrStr {
		//如果是eth以外的erc20，则需要先进行approve操作
		tokenAddr = common.HexToAddress(tokenAddrStr)
		tokenInstance, err := generated.NewBridgeToken(tokenAddr, backend)
		if nil != err {
			return "", err
		}
		auth, err := PrepareAuth(backend, ownerPrivateKey, ownerAddr)
		if nil != err {
			txslog.Error("LockEthErc20Asset", "PrepareAuth err", err.Error())
			return "", err
		}

		prepareDone = true

		//chain33bank 是bridgeBank的基类，所以使用bridgeBank的地址
		tx, err := tokenInstance.Approve(auth, bridgeBankAddr, amount)
		if nil != err {
			return "", err
		}
		err = waitEthTxFinished(backend.(*ethclient.Client), tx.Hash(), "Approve")
		if nil != err {
			return "", err
		}
		txslog.Info("LockEthErc20Asset", "Approve tx with hash", tx.Hash().String())
	}

	prepareDone = false

	auth, err := PrepareAuth(backend, ownerPrivateKey, ownerAddr)
	if nil != err {
		txslog.Error("LockEthErc20Asset", "PrepareAuth err", err.Error())
		return "", err
	}

	prepareDone = true

	if "" == tokenAddrStr {
		auth.Value = amount
	}

	tx, err := bridgeBank.Lock(auth, []byte(chain33Receiver), tokenAddr, amount)
	if nil != err {
		txslog.Error("LockEthErc20Asset", "lock err", err.Error())
		return "", err
	}
	err = waitEthTxFinished(backend.(*ethclient.Client), tx.Hash(), "LockEthErc20Asset")
	if nil != err {
		txslog.Error("LockEthErc20Asset", "waitEthTxFinished err", err.Error())
		return "", err
	}

	return tx.Hash().String(), nil
}

func LockEthErc20AssetAsync(ownerPrivateKeyStr, tokenAddrStr, chain33Receiver string, amount *big.Int, backend bind.ContractBackend, bridgeBank *generated.BridgeBank) (string, error) {
	txslog.Info("LockEthErc20Asset", "ownerPrivateKeyStr", ownerPrivateKeyStr, "tokenAddrStr", tokenAddrStr, "chain33Receiver", chain33Receiver, "amount", amount.String())
	ownerPrivateKey, err := crypto.ToECDSA(common.FromHex(ownerPrivateKeyStr))
	if nil != err {
		return "", err
	}
	ownerAddr := crypto.PubkeyToAddress(ownerPrivateKey.PublicKey)

	auth, err := PrepareAuth(backend, ownerPrivateKey, ownerAddr)
	if nil != err {
		txslog.Error("LockEthErc20Asset", "PrepareAuth err", err.Error())
		return "", err
	}
	//ETH转账，空地址，且设置value
	var tokenAddr common.Address
	if "" == tokenAddrStr {
		auth.Value = amount
	}

	if "" != tokenAddrStr {
		tokenAddr = common.HexToAddress(tokenAddrStr)
	}
	tx, err := bridgeBank.Lock(auth, []byte(chain33Receiver), tokenAddr, amount)
	if nil != err {
		txslog.Error("LockEthErc20Asset", "lock err", err.Error())
		_, err = revokeNonce(ownerAddr)
		if err != nil {
			return "", err
		}
		return "", err
	}
	return tx.Hash().String(), nil
}

/////////////////NewProphecyClaim////////////////
func MakeNewProphecyClaim(newProphecyClaimPara *NewProphecyClaimPara, backend bind.ContractBackend, privateKey *ecdsa.PrivateKey, transactor common.Address, x2EthContracts *X2EthContracts) (string, error) {
	var prepareDone bool
	authVali, err := PrepareAuth(backend, privateKey, transactor)
	if nil != err {
		return "", err
	}

	prepareDone = true

	defer func() {
		if err != nil && prepareDone {
			_, _ = revokeNonce(transactor)
		}
	}()

	amount := newProphecyClaimPara.Amount
	ethReceiver := newProphecyClaimPara.EthReceiver

	// Generate rawHash using ProphecyClaim data
	claimID := crypto.Keccak256Hash(newProphecyClaimPara.Txhash, newProphecyClaimPara.Chain33Sender, newProphecyClaimPara.EthReceiver.Bytes(), newProphecyClaimPara.TokenAddr.Bytes(), amount.Bytes())

	// Sign the hash using the active validator's private key
	signature, err := SignClaim4Eth(claimID, privateKey)
	if nil != err {
		return "", err
	}

	tx, err := x2EthContracts.Oracle.NewOracleClaim(authVali, newProphecyClaimPara.ClaimType, newProphecyClaimPara.Chain33Sender, ethReceiver, newProphecyClaimPara.TokenAddr, newProphecyClaimPara.Symbol, amount, claimID, signature)
	if nil != err {
		return "", err
	}
	err = waitEthTxFinished(backend.(*ethclient.Client), tx.Hash(), "MakeNewProphecyClaim")
	if nil != err {
		return "", err
	}
	return tx.Hash().String(), nil
}
