/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package multichannel

import (
	cb "github.com/hyperledger/fabric-protos-go-apiv2/common"
	"github.com/hyperledger/fabric/common/channelconfig"
	"github.com/hyperledger/fabric/orderer/common/blockcutter"
	"github.com/hyperledger/fabric/orderer/common/msgprocessor"
	mockblockcutter "github.com/hyperledger/fabric/orderer/mocks/common/blockcutter"
	"github.com/hyperledger/fabric/protoutil"
)

// ConsenterSupport is used to mock the multichannel.ConsenterSupport interface
// Whenever a block is written, it writes to the Batches channel to allow for synchronization
type ConsenterSupport struct {
	// SharedConfigVal is the value returned by SharedConfig()
	SharedConfigVal channelconfig.Orderer

	// ChannelConfigVal is the value returned by ChannelConfig()
	ChannelConfigVal channelconfig.Channel

	// BlockCutterVal is the value returned by BlockCutter()
	BlockCutterVal *mockblockcutter.Receiver

	// BlockByIndex maps block numbers to retrieved values of these blocks
	BlockByIndex map[uint64]*cb.Block

	// Blocks is the channel where WriteBlock writes the most recently created block,
	Blocks chan *cb.Block

	// ChannelIDVal is the value returned by ChannelID()
	ChannelIDVal string

	// HeightVal is the value returned by Height()
	HeightVal uint64

	// NextBlockVal stores the block created by the most recent CreateNextBlock() call
	NextBlockVal *cb.Block

	// ClassifyMsgVal is returned by ClassifyMsg
	ClassifyMsgVal msgprocessor.Classification

	// ConfigSeqVal is returned as the configSeq for Process*Msg
	ConfigSeqVal uint64

	// ProcessNormalMsgErr is returned as the error for ProcessNormalMsg
	ProcessNormalMsgErr error

	// ProcessConfigUpdateMsgVal is returned as the error for ProcessConfigUpdateMsg
	ProcessConfigUpdateMsgVal *cb.Envelope

	// ProcessConfigUpdateMsgErr is returned as the error for ProcessConfigUpdateMsg
	ProcessConfigUpdateMsgErr error

	// ProcessConfigMsgVal is returned as the error for ProcessConfigMsg
	ProcessConfigMsgVal *cb.Envelope

	// ProcessConfigMsgErr is returned by ProcessConfigMsg
	ProcessConfigMsgErr error

	// SequenceVal is returned by Sequence
	SequenceVal uint64

	// BlockVerificationErr is returned by VerifyBlockSignature
	BlockVerificationErr error
}

// Block returns the block with the given number or nil if not found
func (mcs *ConsenterSupport) Block(number uint64) *cb.Block {
	return mcs.BlockByIndex[number]
}

// BlockCutter returns BlockCutterVal
func (mcs *ConsenterSupport) BlockCutter() blockcutter.Receiver {
	return mcs.BlockCutterVal
}

// SharedConfig returns SharedConfigVal
func (mcs *ConsenterSupport) SharedConfig() channelconfig.Orderer {
	return mcs.SharedConfigVal
}

// ChannelConfig returns ChannelConfigVal
func (mcs *ConsenterSupport) ChannelConfig() channelconfig.Channel {
	return mcs.ChannelConfigVal
}

// CreateNextBlock creates a simple block structure with the given data
func (mcs *ConsenterSupport) CreateNextBlock(data []*cb.Envelope) *cb.Block {
	block := protoutil.NewBlock(0, nil)
	mtxs := make([][]byte, len(data))
	for i := range data {
		mtxs[i] = protoutil.MarshalOrPanic(data[i])
	}
	block.Data = &cb.BlockData{Data: mtxs}
	mcs.NextBlockVal = block
	return block
}

// WriteBlock writes data to the Blocks channel
func (mcs *ConsenterSupport) WriteBlock(block *cb.Block, encodedMetadataValue []byte) {
	if encodedMetadataValue != nil {
		block.Metadata.Metadata[cb.BlockMetadataIndex_ORDERER] = protoutil.MarshalOrPanic(&cb.Metadata{Value: encodedMetadataValue})
	}
	mcs.Append(block)
}

// WriteConfigBlock calls WriteBlock
func (mcs *ConsenterSupport) WriteConfigBlock(block *cb.Block, encodedMetadataValue []byte) {
	mcs.WriteBlock(block, encodedMetadataValue)
}

// ChannelID returns the channel ID this specific consenter instance is associated with
func (mcs *ConsenterSupport) ChannelID() string {
	return mcs.ChannelIDVal
}

// Height returns the number of blocks of the chain this specific consenter instance is associated with
func (mcs *ConsenterSupport) Height() uint64 {
	return mcs.HeightVal
}

// Sign returns the bytes passed in
func (mcs *ConsenterSupport) Sign(message []byte) ([]byte, error) {
	return message, nil
}

// Serialize returns bytes
func (mcs *ConsenterSupport) Serialize() ([]byte, error) {
	return []byte("creator"), nil
}

// NewSignatureHeader returns an empty signature header
func (mcs *ConsenterSupport) NewSignatureHeader() (*cb.SignatureHeader, error) {
	return &cb.SignatureHeader{}, nil
}

// ClassifyMsg returns ClassifyMsgVal, ClassifyMsgErr
func (mcs *ConsenterSupport) ClassifyMsg(chdr *cb.ChannelHeader) msgprocessor.Classification {
	return mcs.ClassifyMsgVal
}

// ProcessNormalMsg returns ConfigSeqVal, ProcessNormalMsgErr
func (mcs *ConsenterSupport) ProcessNormalMsg(env *cb.Envelope) (configSeq uint64, err error) {
	return mcs.ConfigSeqVal, mcs.ProcessNormalMsgErr
}

// ProcessConfigUpdateMsg returns ProcessConfigUpdateMsgVal, ConfigSeqVal, ProcessConfigUpdateMsgErr
func (mcs *ConsenterSupport) ProcessConfigUpdateMsg(env *cb.Envelope) (config *cb.Envelope, configSeq uint64, err error) {
	return mcs.ProcessConfigUpdateMsgVal, mcs.ConfigSeqVal, mcs.ProcessConfigUpdateMsgErr
}

// ProcessConfigMsg returns ProcessConfigMsgVal, ConfigSeqVal, ProcessConfigMsgErr
func (mcs *ConsenterSupport) ProcessConfigMsg(env *cb.Envelope) (*cb.Envelope, uint64, error) {
	return mcs.ProcessConfigMsgVal, mcs.ConfigSeqVal, mcs.ProcessConfigMsgErr
}

// Sequence returns SequenceVal
func (mcs *ConsenterSupport) Sequence() uint64 {
	return mcs.SequenceVal
}

func (mcs *ConsenterSupport) SignatureVerifier() protoutil.BlockVerifierFunc {
	return func(header *cb.BlockHeader, metadata *cb.BlockMetadata) error {
		return mcs.BlockVerificationErr
	}
}

// Append appends a new block to the ledger in its raw form,
// unlike WriteBlock that also mutates its metadata.
func (mcs *ConsenterSupport) Append(block *cb.Block) error {
	mcs.HeightVal++
	mcs.Blocks <- block
	return nil
}
