// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package executor

import (
	"testing"

	apimock "github.com/33cn/chain33/client/mocks"
	"github.com/33cn/chain33/common/address"
	"github.com/33cn/chain33/common/db"
	"github.com/33cn/chain33/common/db/mocks"
	"github.com/33cn/chain33/types"
	ty "github.com/33cn/plugin/plugin/dapp/relay/types"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"

	_ "github.com/33cn/chain33/system"
)

var chainTestCfg = types.NewChain33Config(types.GetDefaultCfgstring())

func init() {
	Init(ty.RelayX, chainTestCfg, nil)
}

type suiteRelay struct {
	// Include our basic suite logic.
	suite.Suite
	kvdb      *mocks.KVDB
	relay     *relay
	addrRelay string
	orderID   string
	//relayDb   *relayDB
}

func (s *suiteRelay) accountSetup() {
	acc := s.relay.GetCoinsAccount()

	account := &types.Account{
		Balance: 1000 * 1e8,
		Addr:    addrFrom,
	}
	acc.SaveAccount(account)
	account = acc.LoadAccount(addrFrom)
	s.Equal(int64(1000*1e8), account.Balance)
	s.addrRelay = address.ExecAddress("relay")
	_, err := acc.TransferToExec(addrFrom, s.addrRelay, 200*1e8)
	s.Nil(err)
	account = acc.LoadExecAccount(addrFrom, s.addrRelay)
	s.Equal(int64(200*1e8), account.Balance)

	account = &types.Account{
		Balance: 1000 * 1e8,
		Addr:    addrTo,
	}
	acc.SaveAccount(account)
	account = acc.LoadAccount(addrTo)
	s.Equal(int64(1000*1e8), account.Balance)
	s.addrRelay = address.ExecAddress("relay")
	_, err = acc.TransferToExec(addrTo, s.addrRelay, 200*1e8)
	s.Nil(err)
	account = acc.LoadExecAccount(addrTo, s.addrRelay)
	s.Equal(int64(200*1e8), account.Balance)

}

func (s *suiteRelay) SetupSuite() {
	//s.db = new(mocks.KV)
	s.kvdb = new(mocks.KVDB)
	accDb, _ := db.NewGoMemDB("relayTestDb", "test", 128)
	relay := &relay{}
	api := new(apimock.QueueProtocolAPI)
	api.On("GetConfig", mock.Anything).Return(chainTestCfg, nil)
	relay.SetAPI(api)
	relay.SetStateDB(accDb)
	relay.SetLocalDB(s.kvdb)
	relay.SetEnv(10, 100, 1)
	relay.SetIsFree(false)
	relay.SetChild(relay)
	relay.SetExecutorType(types.LoadExecutorType(driverName))
	s.relay = relay

	s.Equal("relay", s.relay.GetName())

	s.accountSetup()
}

func (s *suiteRelay) testExecLocal(tx *types.Transaction, receipt *types.Receipt) {
	s.Equal(int32(types.ExecOk), receipt.Ty)
	rData := &types.ReceiptData{}
	rData.Ty = receipt.Ty
	rData.Logs = append(rData.Logs, receipt.Logs...)

	set, err := s.relay.ExecLocal(tx, rData, 0)
	s.Nil(err)
	order, _ := s.relay.getSellOrderFromDb([]byte(s.orderID))
	var kv []*types.KeyValue
	kv = deleteCreateOrderKeyValue(kv, order, int32(order.PreStatus))
	kv = getCreateOrderKeyValue(kv, order, int32(order.Status))

	s.Subset(set.KV, kv)

}

func (s *suiteRelay) testExecDelLocal(tx *types.Transaction, receipt *types.Receipt) {
	s.Equal(int32(types.ExecOk), receipt.Ty)
	rData := &types.ReceiptData{}
	rData.Ty = receipt.Ty
	rData.Logs = append(rData.Logs, receipt.Logs...)

	set, err := s.relay.ExecDelLocal(tx, rData, 0)
	s.Nil(err)
	order, _ := s.relay.getSellOrderFromDb([]byte(s.orderID))
	var kv []*types.KeyValue
	kv = deleteCreateOrderKeyValue(kv, order, int32(order.Status))
	kv = getCreateOrderKeyValue(kv, order, int32(order.PreStatus))

	s.Subset(set.KV, kv)
}

//create sell
func (s *suiteRelay) TestExec_1() {
	order := &ty.RelayCreate{
		Operation:       ty.RelayOrderSell,
		XCoin:           "BTC",
		XAmount:         0.299 * 1e8,
		XAddr:           addrBtc,
		LocalCoinAmount: 200 * 1e8,
	}

	sell := &ty.RelayAction{
		Ty:    ty.RelayActionCreate,
		Value: &ty.RelayAction_Create{Create: order},
	}

	tx := &types.Transaction{}
	tx.Execer = []byte(ty.RelayX)
	tx.To = address.ExecAddress(ty.RelayX)
	tx.Nonce = 1 //for different order id
	tx.ChainID = chainTestCfg.GetChainID()
	tx.Payload = types.Encode(sell)
	tx.Sign(types.SECP256K1, privFrom)

	s.relay.SetEnv(10, 1000, 1)
	heightBytes := types.Encode(&types.Int64{Data: int64(10)})
	s.kvdb.On("Get", mock.Anything).Return(heightBytes, nil).Once()

	receipt, err := s.relay.Exec(tx, 0)
	s.Nil(err)

	//acc := s.relay.GetCoinsAccount()
	//account := acc.LoadExecAccount(addrFrom, s.addrRelay)
	//s.Equal(int64(200*1e8), account.Frozen)
	//s.Zero(account.Balance)

	var log ty.ReceiptRelayLog
	types.Decode(receipt.Logs[len(receipt.Logs)-1].Log, &log)
	s.Equal("200.0000", log.LocalCoinAmount)
	s.Equal(uint64(10), log.XHeight)

	s.orderID = getRealTxHashID(log.OrderId)

	//s.testExecLocal(tx, receipt)
	//s.testExecDelLocal(tx, receipt)

}

//accept
func (s *suiteRelay) TestExec_2() {
	order := &ty.RelayAccept{
		OrderId: s.orderID,
		XAddr:   addrBtc,
	}

	sell := &ty.RelayAction{
		Ty:    ty.RelayActionAccept,
		Value: &ty.RelayAction_Accept{Accept: order},
	}

	tx := &types.Transaction{}
	tx.Execer = []byte(ty.RelayX)
	tx.To = s.addrRelay
	tx.Payload = types.Encode(sell)
	tx.Sign(types.SECP256K1, privTo)

	s.relay.SetEnv(20, 2000, 1)
	heightBytes := types.Encode(&types.Int64{Data: int64(20)})
	s.kvdb.On("Get", mock.Anything).Return(heightBytes, nil).Once()
	receipt, err := s.relay.Exec(tx, 0)
	s.Nil(err)

	acc := s.relay.GetCoinsAccount()
	account := acc.LoadExecAccount(addrTo, s.addrRelay)
	s.Equal(int64(200*1e8), account.Frozen)
	s.Zero(account.Balance)

	var log ty.ReceiptRelayLog
	types.Decode(receipt.Logs[len(receipt.Logs)-1].Log, &log)
	s.Equal("200.0000", log.LocalCoinAmount)
	s.Equal(uint64(20), log.XHeight)
	s.Equal(ty.RelayOrderStatus_locking.String(), log.CurStatus)

}

//confirm
func (s *suiteRelay) TestExec_3() {

	order := &ty.RelayConfirmTx{
		OrderId: s.orderID,
		TxHash:  "6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4",
	}
	sell := &ty.RelayAction{
		Ty:    ty.RelayActionConfirmTx,
		Value: &ty.RelayAction_ConfirmTx{ConfirmTx: order},
	}

	tx := &types.Transaction{Execer: []byte("relay")}
	tx.To = s.addrRelay
	tx.Payload = types.Encode(sell)
	tx.Sign(types.SECP256K1, privFrom)

	s.relay.SetEnv(30, 3000, 1)
	heightBytes := types.Encode(&types.Int64{Data: int64(30)})
	s.kvdb.On("Get", mock.Anything).Return(heightBytes, nil).Once()
	receipt, err := s.relay.Exec(tx, 0)
	s.Nil(err)

	//acc := s.relay.GetCoinsAccount()
	//account := acc.LoadExecAccount(addrFrom, s.addrRelay)
	//s.Equal(int64(200*1e8),account.Frozen)
	//s.Zero(account.Balance)

	var log ty.ReceiptRelayLog
	types.Decode(receipt.Logs[len(receipt.Logs)-1].Log, &log)
	s.Equal("200.0000", log.LocalCoinAmount)
	s.Equal(uint64(30), log.XHeight)
	s.Equal(ty.RelayOrderStatus_confirming.String(), log.CurStatus)

}

//verify

func (s *suiteRelay) TestExec_4() {
	vout := &ty.Vout{
		Address: "1Am9UTGfdnxabvcywYG2hvzr6qK8T3oUZT",
		Value:   29900000,
	}
	transaction := &ty.BtcTransaction{
		Vout:        []*ty.Vout{vout},
		Time:        2500,
		BlockHeight: 1000,
		Hash:        "6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4",
	}
	strMerkleproof := []string{"e9a66845e05d5abc0ad04ec80f774a7e585c6e8db975962d069a522137b80c1d",
		"ccdafb73d8dcd0173d5d5c3c9a0770d0b3953db889dab99ef05b1907518cb815"}

	proofs := make([][]byte, len(strMerkleproof))
	for i, kk := range strMerkleproof {
		proofs[i], _ = btcHashStrRevers(kk)
	}

	spv := &ty.BtcSpv{
		BranchProof: proofs,
		TxIndex:     2,
		BlockHash:   "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506",
		Height:      100000,
		Hash:        "6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4",
	}

	heightBytes := types.Encode(&types.Int64{Data: int64(1006)})
	s.kvdb.On("Get", mock.Anything).Return(heightBytes, nil).Once()
	var head = &ty.BtcHeader{
		Version:    1,
		MerkleRoot: "f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766",
	}
	headEnc := types.Encode(head)
	s.kvdb.On("Get", mock.Anything).Return(headEnc, nil).Once()

	order := &ty.RelayVerify{
		OrderId: s.orderID,
		Tx:      transaction,
		Spv:     spv,
	}
	sell := &ty.RelayAction{
		Ty:    ty.RelayActionVerifyTx,
		Value: &ty.RelayAction_Verify{Verify: order},
	}
	tx := &types.Transaction{}
	tx.Execer = []byte(ty.RelayX)
	tx.To = s.addrRelay
	tx.Payload = types.Encode(sell)
	tx.Sign(types.SECP256K1, privFrom)

	s.relay.SetEnv(40, 4000, 1)

	_, err := s.relay.Exec(tx, 0)
	s.Nil(err)

	acc := s.relay.GetCoinsAccount()
	account := acc.LoadExecAccount(addrTo, s.addrRelay)
	//s.Equal(int64(200*1e8),account.Balance)
	s.Zero(account.Frozen)
	account = acc.LoadExecAccount(addrFrom, s.addrRelay)
	s.Equal(int64(400*1e8), account.Balance)
	s.Zero(account.Frozen)

	//	s.T().Log("exec4",len(receipt.Logs))
	//var log types.ReceiptRelayLog
	//types.Decode(receipt.Logs[len(receipt.Logs)-1].Log, &log)
	//s.Equal(ty.RelayOrderStatus_finished.String(), log.CurStatus)

}

func (s *suiteRelay) TestExec_9_QryStatus1() {
	addrCoins := &ty.ReqRelayAddrCoins{
		Status: ty.RelayOrderStatus_finished,
		//Coins:[]string{"BTC"},
	}

	var OrderIds [][]byte
	OrderIds = append(OrderIds, []byte(calcRelayOrderID(s.orderID)))
	s.kvdb.On("List", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(OrderIds, nil).Once()
	msg, err := s.relay.Query_GetRelayOrderByStatus(addrCoins)
	s.Nil(err)
	s.T().Log(msg.String())
	s.Contains(msg.String(), "status:finished")
	//s.Equal(ty.RelayOrderStatus_finished,)
}

func (s *suiteRelay) TestExec_9_QryStatus4() {
	addrCoins := &ty.ReqRelayBtcHeaderHeightList{
		ReqHeight: 12,
		Counts:    2,
		Direction: 1,
	}

	var OrderIds [][]byte
	OrderIds = append(OrderIds, []byte(s.orderID))
	s.kvdb.On("List", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(OrderIds, nil).Once()
	msg, err := s.relay.Query_GetBTCHeaderList(addrCoins)
	s.Nil(err)
	//s.T().Log(msg)
	s.Contains(msg.String(), "heights:-1")
}

func (s *suiteRelay) TestExec_9_QryStatus5() {
	addrCoins := &ty.ReqRelayQryBTCHeadHeight{
		BaseHeight: 10,
	}

	heightBytes := types.Encode(&types.Int64{Data: int64(10)})
	s.kvdb.On("Get", mock.Anything).Return(heightBytes, nil).Twice()
	msg, err := s.relay.Query_GetBTCHeaderCurHeight(addrCoins)
	s.Nil(err)
	//s.T().Log(msg)
	s.Contains(msg.String(), "curHeight:10 baseHeight:10")
}

func TestRunSuiteRelay(t *testing.T) {
	log := new(suiteRelay)
	suite.Run(t, log)
}

//////////////////////////////////////

type suiteBtcHeader struct {
	// Include our basic suite logic.
	suite.Suite
	db    *mocks.KV
	kvdb  *mocks.KVDB
	relay *relay
}

func (s *suiteBtcHeader) SetupSuite() {
	s.db = new(mocks.KV)
	s.kvdb = new(mocks.KVDB)
	//accDb, _ := db.NewGoMemDB("relayTestDb", "test", 128)
	relay := &relay{}
	api := new(apimock.QueueProtocolAPI)
	api.On("GetConfig", mock.Anything).Return(chainTestCfg, nil)
	relay.SetAPI(api)
	relay.SetStateDB(s.db)
	relay.SetLocalDB(s.kvdb)
	relay.SetEnv(10, 100, 1)
	relay.SetIsFree(false)
	relay.SetChild(relay)
	relay.SetExecutorType(types.LoadExecutorType(driverName))
	s.relay = relay

	s.Equal("relay", s.relay.GetName())

}

func (s *suiteBtcHeader) testExecBtcHeadLocal(tx *types.Transaction, receipt *types.Receipt, headers *ty.BtcHeaders) {
	s.Equal(int32(types.ExecOk), receipt.Ty)
	rData := &types.ReceiptData{}
	rData.Ty = receipt.Ty
	rData.Logs = append(rData.Logs, receipt.Logs...)
	s.kvdb.On("Get", mock.Anything).Return([]byte{}, nil).Twice()
	set, err := s.relay.ExecLocal(tx, rData, 0)
	s.Nil(err)

	var kv []*types.KeyValue
	btc := newBtcStore(s.relay.GetLocalDB())
	for _, head := range headers.BtcHeader {
		val, err := btc.saveBlockHead(head)
		s.Nil(err)
		kv = append(kv, val...)

	}

	s.Subset(set.KV, kv)

}

func (s *suiteBtcHeader) testExecBtcHeadDelLocal(tx *types.Transaction, receipt *types.Receipt, headers *ty.BtcHeaders) {
	s.Equal(int32(types.ExecOk), receipt.Ty)
	rData := &types.ReceiptData{}
	rData.Ty = receipt.Ty
	rData.Logs = append(rData.Logs, receipt.Logs...)
	s.kvdb.On("Get", mock.Anything).Return([]byte{}, nil).Twice()
	set, err := s.relay.ExecDelLocal(tx, rData, 0)
	s.Nil(err)

	var kv []*types.KeyValue
	btc := newBtcStore(s.relay.GetLocalDB())
	for _, head := range headers.BtcHeader {
		val, err := btc.delBlockHead(head)
		s.Nil(err)
		kv = append(kv, val...)

	}

	s.Subset(set.KV, kv)
}

//rcv btchead
func (s *suiteBtcHeader) TestSaveBtcHead_1() {
	head0 := &ty.BtcHeader{
		Hash:          "5e7d9c599cd040ec2ba53f4dee28028710be8c135e779f65c56feadaae34c3f2",
		Confirmations: 92,
		Height:        10,
		Version:       536870912,
		MerkleRoot:    "ab91cd4160e1379c337eee6b7a4bdbb7399d70268d86045aba150743c00c90b6",
		Time:          1530862108,
		Nonce:         0,
		Bits:          545259519,
		Difficulty:    0,
		PreviousHash:  "604efe53975ab06cad8748fd703ad5bc960e8b752b2aae98f0f871a4a05abfc7",
	}
	head1 := &ty.BtcHeader{
		Hash:          "7b7a4a9b49db5a1162be515d380cd186e98c2bf0bb90f1145485d7c43343fc7c",
		Confirmations: 91,
		Height:        11,
		Version:       536870912,
		MerkleRoot:    "cfa9b66696aea63b7266ffaa1cb4b96c8dd6959eaabf2eb14173f4adaa551f6f",
		Time:          1530862108,
		Nonce:         1,
		Bits:          545259519,
		Difficulty:    0,
		PreviousHash:  "5e7d9c599cd040ec2ba53f4dee28028710be8c135e779f65c56feadaae34c3f2",
	}

	head2 := &ty.BtcHeader{
		Hash:          "57bd2805725dd2d102708af4c8f6eb67cd0b3de6dd531f59fbc7d441a0388b6e",
		Confirmations: 90,
		Height:        12,
		Version:       536870912,
		MerkleRoot:    "f549e7024c07b741d34633654847be8778d2ccc9b1b17571914050060cc785f6",
		Time:          1530862108,
		Nonce:         4,
		Bits:          545259519,
		Difficulty:    0,
		PreviousHash:  "7b7a4a9b49db5a1162be515d380cd186e98c2bf0bb90f1145485d7c43343fc7c",
	}

	headers := &ty.BtcHeaders{}
	headers.BtcHeader = append(headers.BtcHeader, head0)
	headers.BtcHeader = append(headers.BtcHeader, head1)
	headers.BtcHeader = append(headers.BtcHeader, head2)

	sell := &ty.RelayAction{
		Ty:    ty.RelayActionRcvBTCHeaders,
		Value: &ty.RelayAction_BtcHeaders{BtcHeaders: headers},
	}

	tx := &types.Transaction{}
	tx.Execer = []byte(ty.RelayX)
	tx.To = address.ExecAddress(ty.RelayX)
	tx.Nonce = 2 //for different order id
	tx.ChainID = chainTestCfg.GetChainID()

	tx.Payload = types.Encode(sell)
	tx.Sign(types.SECP256K1, privFrom)

	s.relay.SetEnv(10, 1000, 1)

	s.db.On("Get", mock.Anything).Return(nil, types.ErrNotFound).Once()
	s.db.On("Set", mock.Anything, mock.Anything).Return(nil).Once()
	receipt, err := s.relay.Exec(tx, 0)
	s.Nil(err)

	s.testExecBtcHeadLocal(tx, receipt, headers)
	s.testExecBtcHeadDelLocal(tx, receipt, headers)

}

func TestRunSuiteBtcHeader(t *testing.T) {
	log := new(suiteBtcHeader)
	suite.Run(t, log)
}
