package protocol

import (
	"github.com/vapor/consensus"
	"github.com/vapor/errors"
	"github.com/vapor/protocol/bc"
	"github.com/vapor/protocol/bc/types"
	"github.com/vapor/protocol/state"
)

var (
	errNotFoundConsensusNode = errors.New("can not found consensus node")
	errNotFoundBlockNode     = errors.New("can not find block node")
)

func (c *Chain) getBestConsensusResult() (*state.ConsensusResult, error) {
	bestBlockHeader := c.bestBlockHeader
	seq := state.CalcVoteSeq(bestBlockHeader.Height)
	return c.getConsensusResult(seq, bestBlockHeader)
}

func getBlockerOrder(startTimestamp, blockTimestamp, numOfConsensusNode uint64) uint64 {
	// One round of product block time for all consensus nodes
	roundBlockTime := consensus.ActiveNetParams.BlockNumEachNode * numOfConsensusNode * consensus.ActiveNetParams.BlockTimeInterval
	// The start time of the last round of product block
	lastRoundStartTime := startTimestamp + (blockTimestamp-startTimestamp)/roundBlockTime*roundBlockTime
	// Order of blocker
	return (blockTimestamp - lastRoundStartTime) / (consensus.ActiveNetParams.BlockNumEachNode * consensus.ActiveNetParams.BlockTimeInterval)
}

func (c *Chain) getConsensusNode(prevBlockHash *bc.Hash, pubkey string) (*state.ConsensusNode, error) {
	consensusNodeMap, err := c.getConsensusNodes(prevBlockHash)
	if err != nil {
		return nil, err
	}

	node, exist := consensusNodeMap[pubkey]
	if !exist {
		return nil, errNotFoundConsensusNode
	}
	return node, nil
}

func (c *Chain) getConsensusNodes(prevBlockHash *bc.Hash) (map[string]*state.ConsensusNode, error) {
	prevBlockHeader, err := c.store.GetBlockHeader(prevBlockHash)
	if err != nil {
		return nil, errNotFoundBlockNode
	}

	bestBlockHeader := c.bestBlockHeader
	preSeq := state.CalcVoteSeq(prevBlockHeader.Height+1) - 1
	if bestSeq := state.CalcVoteSeq(bestBlockHeader.Height); preSeq > bestSeq {
		preSeq = bestSeq
	}

	lastBlockHeader, err := c.getPrevRoundLastBlock(prevBlockHash)
	if err != nil {
		return nil, err
	}

	consensusResult, err := c.getConsensusResult(preSeq, lastBlockHeader)
	if err != nil {
		return nil, err
	}

	return consensusResult.ConsensusNodes()
}

// getConsensusResult return the vote result
// seq represent the sequence of vote
// blockHeader represent the chain in which the result of the vote is located
// Voting results need to be adjusted according to the chain
func (c *Chain) getConsensusResult(seq uint64, blockHeader *types.BlockHeader) (*state.ConsensusResult, error) {
	consensusResult, err := c.store.GetConsensusResult(seq)
	if err != nil {
		return nil, err
	}

	if err := c.reorganizeConsensusResult(consensusResult, blockHeader); err != nil {
		return nil, err
	}

	return consensusResult, nil
}

func (c *Chain) getPrevRoundLastBlock(prevBlockHash *bc.Hash) (*types.BlockHeader, error) {
	blockHeader, err := c.store.GetBlockHeader(prevBlockHash)
	if err != nil {
		return nil, errNotFoundBlockNode
	}

	for blockHeader.Height%consensus.ActiveNetParams.RoundVoteBlockNums != 0 {
		blockHeader, err = c.store.GetBlockHeader(&blockHeader.PreviousBlockHash)
		if err != nil {
			return nil, err
		}
	}
	return blockHeader, nil
}

func (c *Chain) reorganizeConsensusResult(consensusResult *state.ConsensusResult, blockHeader *types.BlockHeader) error {
	mainChainBlockHeader, err := c.store.GetBlockHeader(&consensusResult.BlockHash)
	if err != nil {
		return err
	}

	attachBlockHeaders, detachBlockHeaders, err := c.calcReorganizeChain(blockHeader, mainChainBlockHeader)
	if err != nil {
		return err
	}

	for _, bh := range detachBlockHeaders {
		blockHash := bh.Hash()
		block, err := c.store.GetBlock(&blockHash)
		if err != nil {
			return err
		}

		if err := consensusResult.DetachBlock(block); err != nil {
			return err
		}
	}

	for _, bh := range attachBlockHeaders {
		blockHash := bh.Hash()
		block, err := c.store.GetBlock(&blockHash)
		if err != nil {
			return err
		}

		if err := consensusResult.ApplyBlock(block); err != nil {
			return err
		}
	}
	return nil
}

// GetBlocker return blocker by specified timestamp
func (c *Chain) GetBlocker(prevBlockHash *bc.Hash, timeStamp uint64) (string, error) {
	consensusNodeMap, err := c.getConsensusNodes(prevBlockHash)
	if err != nil {
		return "", err
	}

	prevVoteRoundLastBlock, err := c.getPrevRoundLastBlock(prevBlockHash)
	if err != nil {
		return "", err
	}

	startTimestamp := prevVoteRoundLastBlock.Timestamp + consensus.ActiveNetParams.BlockTimeInterval
	order := getBlockerOrder(startTimestamp, timeStamp, uint64(len(consensusNodeMap)))
	for xPub, consensusNode := range consensusNodeMap {
		if consensusNode.Order == order {
			return xPub, nil
		}
	}

	// impossible occur
	return "", errors.New("can not find blocker by given timestamp")
}

// GetConsensusResultByHash return vote result by block hash
func (c *Chain) GetConsensusResultByHash(blockHash *bc.Hash) (*state.ConsensusResult, error) {
	blockHeader, err := c.store.GetBlockHeader(blockHash)
	if err != nil {
		return nil, err
	}
	return c.getConsensusResult(state.CalcVoteSeq(blockHeader.Height), blockHeader)
}
