// 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 (
	"bytes"
	"math/big"
	"strings"
	"time"

	"github.com/33cn/chain33/common"
	dbm "github.com/33cn/chain33/common/db"
	"github.com/33cn/chain33/common/difficulty"
	"github.com/33cn/chain33/common/merkle"
	"github.com/33cn/chain33/types"
	ty "github.com/33cn/plugin/plugin/dapp/relay/types"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/wire"
	"github.com/golang/protobuf/proto"
)

type btcStore struct {
	db dbm.KVDB
}

func newBtcStore(db dbm.KVDB) *btcStore {
	return &btcStore{db: db}
}

func (b *btcStore) getBtcHeadHeightFromDb(key []byte) (int64, error) {
	val, err := b.db.Get(key)
	if err != nil {
		return -1, err
	}

	height, err := decodeHeight(val)
	if err != nil {
		return -1, err
	}

	return height, nil
}

func (b *btcStore) getLastBtcHeadHeight() (int64, error) {
	key := relayBTCHeaderLastHeight
	return b.getBtcHeadHeightFromDb(key)
}

func (b *btcStore) getBtcHeadByHeight(height int64) (*ty.BtcHeader, error) {
	var head ty.BtcHeader
	key := calcBtcHeaderKeyHeight(height)
	val, err := b.db.Get(key)
	if err != nil {
		return nil, err
	}
	err = types.Decode(val, &head)
	if err != nil {
		return nil, err
	}

	return &head, nil
}

func (b *btcStore) getLastBtcHead() (*ty.BtcHeader, error) {
	height, err := b.getLastBtcHeadHeight()
	if err != nil {
		return nil, err
	}

	head, err := b.getBtcHeadByHeight(height)
	if err != nil {
		return nil, err
	}

	return head, nil
}

func (b *btcStore) saveBlockHead(head *ty.BtcHeader) ([]*types.KeyValue, error) {
	var kv []*types.KeyValue
	var key []byte

	val, err := proto.Marshal(head)
	if err != nil {
		relaylog.Error("saveBlockHead", "height", head.Height, "hash", head.Hash)
		return nil, err

	}

	// hash:header
	key = calcBtcHeaderKeyHash(head.Hash)
	kv = append(kv, &types.KeyValue{Key: key, Value: val})
	// height:header
	key = calcBtcHeaderKeyHeight(int64(head.Height))
	kv = append(kv, &types.KeyValue{Key: key, Value: val})

	// prefix-height:height
	key = calcBtcHeaderKeyHeightList(int64(head.Height))
	heightBytes := types.Encode(&types.Int64{Data: int64(head.Height)})
	kv = append(kv, &types.KeyValue{Key: key, Value: heightBytes})

	return kv, nil
}

func (b *btcStore) saveBlockLastHead(head *ty.ReceiptRelayRcvBTCHeaders) ([]*types.KeyValue, error) {
	var kv []*types.KeyValue

	heightBytes := types.Encode(&types.Int64{Data: int64(head.NewHeight)})
	key := relayBTCHeaderLastHeight
	kv = append(kv, &types.KeyValue{Key: key, Value: heightBytes})

	heightBytes = types.Encode(&types.Int64{Data: int64(head.NewBaseHeight)})
	key = relayBTCHeaderBaseHeight
	kv = append(kv, &types.KeyValue{Key: key, Value: heightBytes})

	return kv, nil
}

func (b *btcStore) delBlockHead(head *ty.BtcHeader) ([]*types.KeyValue, error) {
	var kv []*types.KeyValue

	key := calcBtcHeaderKeyHash(head.Hash)
	kv = append(kv, &types.KeyValue{Key: key, Value: nil})
	// height:header
	key = calcBtcHeaderKeyHeight(int64(head.Height))
	kv = append(kv, &types.KeyValue{Key: key, Value: nil})

	// prefix-height:height
	key = calcBtcHeaderKeyHeightList(int64(head.Height))
	kv = append(kv, &types.KeyValue{Key: key, Value: nil})

	return kv, nil
}

func (b *btcStore) delBlockLastHead(head *ty.ReceiptRelayRcvBTCHeaders) ([]*types.KeyValue, error) {
	var kv []*types.KeyValue
	var key []byte

	heightBytes := types.Encode(&types.Int64{Data: int64(head.LastHeight)})
	key = relayBTCHeaderLastHeight
	kv = append(kv, &types.KeyValue{Key: key, Value: heightBytes})

	heightBytes = types.Encode(&types.Int64{Data: int64(head.LastBaseHeight)})
	key = relayBTCHeaderBaseHeight
	kv = append(kv, &types.KeyValue{Key: key, Value: heightBytes})

	return kv, nil
}

func decodeHeight(heightBytes []byte) (int64, error) {
	var height types.Int64
	err := types.Decode(heightBytes, &height)
	if err != nil {
		return -1, err
	}
	return height.Data, nil
}

func (b *btcStore) getBtcCurHeight(req *ty.ReqRelayQryBTCHeadHeight) (types.Message, error) {

	height, err := b.getLastBtcHeadHeight()
	if err == types.ErrNotFound {
		height = -1
	} else if err != nil {
		return nil, err
	}

	key := relayBTCHeaderBaseHeight
	baseHeight, err := b.getBtcHeadHeightFromDb(key)
	if err == types.ErrNotFound {
		baseHeight = -1
	} else if err != nil {
		return nil, err
	}
	var replay ty.ReplayRelayQryBTCHeadHeight
	replay.CurHeight = height
	replay.BaseHeight = baseHeight
	return &replay, nil
}

func (b *btcStore) getMerkleRootFromHeader(blockhash string) (string, error) {
	value, err := b.db.Get(calcBtcHeaderKeyHash(blockhash))
	if err != nil {
		return "", err
	}

	var header ty.BtcHeader
	if err = types.Decode(value, &header); err != nil {
		return "", err
	}

	return header.MerkleRoot, nil

}

func (b *btcStore) verifyBtcTx(verify *ty.RelayVerify, order *ty.RelayOrder) error {
	var foundtx bool
	for _, outtx := range verify.GetTx().GetVout() {
		if outtx.Address == order.XAddr && outtx.Value >= order.XAmount {
			foundtx = true
		}
	}

	if !foundtx {
		return ty.ErrRelayVerifyAddrNotFound
	}

	acceptTime := time.Unix(order.AcceptTime, 0)
	txTime := time.Unix(verify.GetTx().Time, 0)
	confirmTime := time.Unix(order.ConfirmTime, 0)

	if txTime.Sub(acceptTime) < 0 || confirmTime.Sub(txTime) < 0 {
		relaylog.Error("verifyTx", "tx time not correct to accept", txTime.Sub(acceptTime), "to confirm time", confirmTime.Sub(txTime))
		return ty.ErrRelayBtcTxTimeErr
	}

	height, err := b.getLastBtcHeadHeight()
	if err != nil {
		return err
	}

	if verify.Tx.BlockHeight+uint64(order.XBlockWaits) > uint64(height) {
		return ty.ErrRelayWaitBlocksErr
	}

	rawHash, err := btcHashStrRevers(verify.GetTx().GetHash())
	if err != nil {
		return err
	}
	sibs := verify.GetSpv().GetBranchProof()

	verifyRoot := merkle.GetMerkleRootFromBranch(sibs, rawHash, verify.GetSpv().GetTxIndex())
	str, err := b.getMerkleRootFromHeader(verify.GetSpv().GetBlockHash())
	if err != nil {
		return err
	}
	realMerkleRoot, err := btcHashStrRevers(str)
	if err != nil {
		return err
	}

	rst := bytes.Equal(realMerkleRoot, verifyRoot)
	if !rst {
		return ty.ErrRelayVerify
	}

	return nil

}

func (b *btcStore) verifyCmdBtcTx(verify *ty.RelayVerifyCli) error {
	rawhash, err := getRawTxHash(verify.RawTx)
	if err != nil {
		return err
	}
	sibs, err := getSiblingHash(verify.MerkBranch)
	if err != nil {
		return err
	}

	verifymerkleroot := merkle.GetMerkleRootFromBranch(sibs, rawhash, verify.TxIndex)
	str, err := b.getMerkleRootFromHeader(verify.BlockHash)
	if err != nil {
		return err
	}
	realmerkleroot, err := btcHashStrRevers(str)
	if err != nil {
		return err
	}

	rst := bytes.Equal(realmerkleroot, verifymerkleroot)
	if !rst {
		return ty.ErrRelayVerify
	}

	return nil
}

func getRawTxHash(rawtx string) ([]byte, error) {
	data, err := common.FromHex(rawtx)
	if err != nil {
		return nil, err
	}
	h := common.Sha2Sum(data)
	return h, nil
}

func getSiblingHash(sibling string) ([][]byte, error) {
	var err error
	sibsarr := strings.Split(sibling, "-")

	sibs := make([][]byte, len(sibsarr))
	for i, val := range sibsarr {
		sibs[i], err = btcHashStrRevers(val)
		if err != nil {
			return nil, err
		}

	}
	return sibs[:][:], nil
}

func btcHashStrRevers(str string) ([]byte, error) {
	data, err := common.FromHex(str)
	if err != nil {
		return nil, err
	}
	merkle := reverse(data)
	return merkle, nil
}

func reverse(h []byte) []byte {
	for i := 0; i < common.Sha256Len/2; i++ {
		h[i], h[common.Sha256Len-1-i] = h[common.Sha256Len-1-i], h[i]
	}
	return h
}

func (b *btcStore) getHeadHeightList(req *ty.ReqRelayBtcHeaderHeightList) (types.Message, error) {
	prefix := []byte(relayBTCHeaderHeightList)
	key := calcBtcHeaderKeyHeightList(req.ReqHeight)

	values, err := b.db.List(prefix, key, req.Counts, req.Direction)
	if err != nil {
		values, err = b.db.List(prefix, nil, req.Counts, req.Direction)
		if err != nil {
			return nil, err
		}
	}

	var replay ty.ReplyRelayBtcHeadHeightList
	heightGot := make(map[int64]bool)
	for _, heightByte := range values {
		height, _ := decodeHeight(heightByte)
		if !heightGot[height] {
			replay.Heights = append(replay.Heights, height)
			heightGot[height] = true
		}
	}

	return &replay, nil

}

func btcWireHeader(head *ty.BtcHeader) (*wire.BlockHeader, error) {
	preHash, err := chainhash.NewHashFromStr(head.PreviousHash)
	if err != nil {
		return nil, err
	}
	merkleRoot, err := chainhash.NewHashFromStr(head.MerkleRoot)
	if err != nil {
		return nil, err
	}

	h := &wire.BlockHeader{}
	h.Version = int32(head.Version)
	h.PrevBlock = *preHash
	h.MerkleRoot = *merkleRoot
	h.Bits = uint32(head.Bits)
	h.Nonce = uint32(head.Nonce)
	h.Timestamp = time.Unix(head.Time, 0)

	return h, nil
}

func verifyBlockHeader(head *ty.BtcHeader, preHead *ty.RelayLastRcvBtcHeader, localDb dbm.KVDB) error {
	if head == nil {
		return types.ErrInvalidParam
	}

	if preHead != nil && preHead.Header != nil && (preHead.Header.Hash != head.PreviousHash || preHead.Header.Height+1 != head.Height) && !head.IsReset {

		return ty.ErrRelayBtcHeadSequenceErr
	}

	//real BTC block not change the bits before height<30000, not match with the calculation result
	if !head.IsReset && head.Height > 30000 {
		newBits, err := calcNextRequiredDifficulty(preHead.Header, localDb)
		if err != nil && err != types.ErrNotFound {
			return err
		}

		if newBits != 0 && newBits != head.Bits {
			return ty.ErrRelayBtcHeadNewBitsErr
		}
	}

	btcHeader, err := btcWireHeader(head)
	if err != nil {
		return err
	}
	hash := btcHeader.BlockHash()

	if hash.String() != head.Hash {
		return ty.ErrRelayBtcHeadHashErr
	}

	target := difficulty.CompactToBig(uint32(head.Bits))

	// The block hash must be less than the claimed target.
	hashNum := difficulty.HashToBig(hash[:])
	if hashNum.Cmp(target) > 0 {
		return ty.ErrRelayBtcHeadBitsErr
	}

	return nil
}

// refer to btcd's blockchain's calcNextRequiredDifficulty() function
// calcNextRequiredDifficulty calculates the required difficulty for the block
// after the passed previous block node based on the difficulty retarget rules.
func calcNextRequiredDifficulty(preHead *ty.BtcHeader, localDb dbm.KVDB) (int64, error) {
	if preHead == nil {
		return 0, nil
	}

	// Genesis block.
	targetTimespan := time.Hour * 24 * 14  // 14 days
	TargetTimePerBlock := time.Minute * 10 // 10 minutes
	retargetAdjustmentFactor := int64(4)   // 25% less, 400% more
	timeSpan := int64(targetTimespan / time.Second)
	timeBlock := int64(TargetTimePerBlock / time.Second)

	// powLimit is the highest proof of work value a Bitcoin block
	// can have for the regression test network.  It is the value 2^255 - 1.
	bigOne := big.NewInt(1)
	powLimit := new(big.Int).Sub(new(big.Int).Lsh(bigOne, 255), bigOne)
	blocksPerRetarget := uint64(timeSpan / timeBlock)
	minRetargetTimespan := timeSpan / retargetAdjustmentFactor
	maxRetargetTimespan := timeSpan * retargetAdjustmentFactor

	// Return the previous block's difficulty requirements if this block
	// is not at a difficulty retarget interval.
	if (preHead.Height+1)%blocksPerRetarget != 0 {
		// For networks that support it, allow special reduction of the
		// required difficulty once too much time has elapsed without
		// mining a block.

		// For the main network (or any unrecognized networks), simply
		// return the previous block's difficulty requirements.
		return preHead.Bits, nil
	}

	// Get the block node at the previous retarget (targetTimespan days
	// worth of blocks).
	btc := newBtcStore(localDb)
	firstHead, err := btc.getBtcHeadByHeight(int64(preHead.Height - (blocksPerRetarget - 1)))
	if err != nil {
		return 0, err
	}

	// Limit the amount of adjustment that can occur to the previous
	// difficulty.
	actualTimespan := preHead.Time - firstHead.Time
	adjustedTimespan := actualTimespan
	if actualTimespan < minRetargetTimespan {
		adjustedTimespan = minRetargetTimespan
	} else if actualTimespan > maxRetargetTimespan {
		adjustedTimespan = maxRetargetTimespan
	}

	// Calculate new target difficulty as:
	//  currentDifficulty * (adjustedTimespan / targetTimespan)
	// The result uses integer division which means it will be slightly
	// rounded down.  Bitcoind also uses integer division to calculate this
	// result.
	oldTarget := difficulty.CompactToBig(uint32(preHead.Bits))
	newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan))
	newTarget.Div(newTarget, big.NewInt(timeSpan))

	// Limit new value to the proof of work limit.
	if newTarget.Cmp(powLimit) > 0 {
		newTarget.Set(powLimit)
	}

	newTargetBits := difficulty.BigToCompact(newTarget)

	return int64(newTargetBits), nil
}
