package proposal

import (
	"testing"

	"github.com/vapor/consensus"
	"github.com/vapor/protocol/bc"
	"github.com/vapor/protocol/bc/types"
	"github.com/vapor/protocol/state"
	"github.com/vapor/testutil"
)

func TestCalCoinbaseTxReward(t *testing.T) {
	consensus.ActiveNetParams.ProducerSubsidys = []consensus.ProducerSubsidy{
		{BeginBlock: 0, EndBlock: 0, Subsidy: 24},
		{BeginBlock: 1, EndBlock: 840000, Subsidy: 24},
		{BeginBlock: 840001, EndBlock: 1680000, Subsidy: 12},
		{BeginBlock: 1680001, EndBlock: 3360000, Subsidy: 6},
	}
	reductionInterval := uint64(840000)

	cases := []struct {
		desc       string
		block      *types.Block
		wantReward state.CoinbaseReward
	}{
		{
			desc: "the block height is reductionInterval - 1",
			block: &types.Block{
				BlockHeader: types.BlockHeader{
					Height: reductionInterval - 1,
				},
				Transactions: []*types.Tx{
					&types.Tx{
						TxData: types.TxData{
							Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
							Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
						},
					},
				},
			},
			wantReward: state.CoinbaseReward{
				Amount:         24,
				ControlProgram: []byte{0x51},
			},
		},
		{
			desc: "the block height is reductionInterval",
			block: &types.Block{
				BlockHeader: types.BlockHeader{
					Height: reductionInterval,
				},
				Transactions: []*types.Tx{
					&types.Tx{
						TxData: types.TxData{
							Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
							Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
						},
					},
				},
			},
			wantReward: state.CoinbaseReward{
				Amount:         24,
				ControlProgram: []byte{0x51},
			},
		},
		{
			desc: "the block height is reductionInterval + 1",
			block: &types.Block{
				BlockHeader: types.BlockHeader{
					Height: reductionInterval + 1,
				},
				Transactions: []*types.Tx{
					&types.Tx{
						TxData: types.TxData{
							Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
							Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
						},
					},
				},
			},
			wantReward: state.CoinbaseReward{
				Amount:         12,
				ControlProgram: []byte{0x51},
			},
		},
		{
			desc: "the block height is reductionInterval * 2",
			block: &types.Block{
				BlockHeader: types.BlockHeader{
					Height: reductionInterval * 2,
				},
				Transactions: []*types.Tx{
					&types.Tx{
						TxData: types.TxData{
							Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
							Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
						},
					},
				},
			},
			wantReward: state.CoinbaseReward{
				Amount:         12,
				ControlProgram: []byte{0x51},
			},
		},
		{
			desc: "the block height is 2*reductionInterval + 1",
			block: &types.Block{
				BlockHeader: types.BlockHeader{
					Height: 2*reductionInterval + 1,
				},
				Transactions: []*types.Tx{
					&types.Tx{
						TxData: types.TxData{
							Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
							Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
						},
					},
				},
			},
			wantReward: state.CoinbaseReward{
				Amount:         6,
				ControlProgram: []byte{0x51},
			},
		},
	}

	for i, c := range cases {
		gotReward, err := state.CalCoinbaseReward(c.block)
		if err != nil {
			t.Fatal(err)
		}

		if !testutil.DeepEqual(*gotReward, c.wantReward) {
			t.Fatalf("test case %d: %s, the coinbase reward got: %v, want: %v", i, c.desc, *gotReward, c.wantReward)
		}
	}
}

func TestCountCoinbaseTxRewards(t *testing.T) {
	consensus.ActiveNetParams.ProducerSubsidys = []consensus.ProducerSubsidy{
		{BeginBlock: 0, EndBlock: 0, Subsidy: 24},
		{BeginBlock: 1, EndBlock: 840000, Subsidy: 24},
		{BeginBlock: 840001, EndBlock: 1680000, Subsidy: 12},
		{BeginBlock: 1680001, EndBlock: 3360000, Subsidy: 6},
	}

	cases := []struct {
		desc                string
		block               *types.Block
		consensusResult     *state.ConsensusResult
		wantRewards         []state.CoinbaseReward
		wantConsensusResult *state.ConsensusResult
	}{
		{
			desc: "the block height is RoundVoteBlockNums - 1",
			block: &types.Block{
				BlockHeader: types.BlockHeader{
					Height: consensus.ActiveNetParams.RoundVoteBlockNums - 1,
				},
				Transactions: []*types.Tx{nil},
			},
			consensusResult: &state.ConsensusResult{
				CoinbaseReward: map[string]uint64{
					"51": 10,
				},
			},
			wantRewards: []state.CoinbaseReward{},
			wantConsensusResult: &state.ConsensusResult{
				CoinbaseReward: map[string]uint64{
					"51": 34,
				},
			},
		},
		{
			desc: "the block height is RoundVoteBlockNums",
			block: &types.Block{
				BlockHeader: types.BlockHeader{
					Height: consensus.ActiveNetParams.RoundVoteBlockNums,
				},
				Transactions: []*types.Tx{nil},
			},
			consensusResult: &state.ConsensusResult{
				CoinbaseReward: map[string]uint64{
					"51": 10,
					"52": 20,
				},
			},
			wantRewards: []state.CoinbaseReward{},
			wantConsensusResult: &state.ConsensusResult{
				CoinbaseReward: map[string]uint64{
					"51": 34,
					"52": 20,
				},
			},
		},
		{
			desc: "the block height is RoundVoteBlockNums + 1",
			block: &types.Block{
				BlockHeader: types.BlockHeader{
					Height: consensus.ActiveNetParams.RoundVoteBlockNums + 1,
				},
				Transactions: []*types.Tx{nil},
			},
			consensusResult: &state.ConsensusResult{
				CoinbaseReward: map[string]uint64{
					"51": 10,
				},
			},
			wantRewards: []state.CoinbaseReward{
				state.CoinbaseReward{
					Amount:         10,
					ControlProgram: []byte{0x51},
				},
			},
			wantConsensusResult: &state.ConsensusResult{
				CoinbaseReward: map[string]uint64{
					"51": 24,
				},
			},
		},
		{
			desc: "the block height is RoundVoteBlockNums * 2",
			block: &types.Block{
				BlockHeader: types.BlockHeader{
					Height: consensus.ActiveNetParams.RoundVoteBlockNums * 2,
				},
				Transactions: []*types.Tx{nil},
			},
			consensusResult: &state.ConsensusResult{
				CoinbaseReward: map[string]uint64{
					"51": 10,
				},
			},
			wantRewards: []state.CoinbaseReward{},
			wantConsensusResult: &state.ConsensusResult{
				CoinbaseReward: map[string]uint64{
					"51": 34,
				},
			},
		},
		{
			desc: "the block height is 2*RoundVoteBlockNums + 1",
			block: &types.Block{
				BlockHeader: types.BlockHeader{
					Height: 2*consensus.ActiveNetParams.RoundVoteBlockNums + 1,
				},
				Transactions: []*types.Tx{nil},
			},
			consensusResult: &state.ConsensusResult{
				CoinbaseReward: map[string]uint64{
					"51": 10,
					"52": 20,
				},
			},
			wantRewards: []state.CoinbaseReward{
				state.CoinbaseReward{
					Amount:         20,
					ControlProgram: []byte{0x52},
				},
				state.CoinbaseReward{
					Amount:         10,
					ControlProgram: []byte{0x51},
				},
			},
			wantConsensusResult: &state.ConsensusResult{
				CoinbaseReward: map[string]uint64{
					"51": 24,
				},
			},
		},
	}

	for i, c := range cases {
		rewards, err := c.consensusResult.GetCoinbaseRewards(c.block.Height - 1)
		if err != nil {
			t.Fatal(err)
		}

		if !testutil.DeepEqual(rewards, c.wantRewards) {
			t.Fatalf("test case %d: %s, the coinbase reward got: %v, want: %v", i, c.desc, rewards, c.wantRewards)
		}

		// create coinbase transaction
		c.block.Transactions[0], err = createCoinbaseTx(nil, c.block.Height, rewards)
		if err != nil {
			t.Fatal(err)
		}

		if err := c.consensusResult.AttachCoinbaseReward(c.block); err != nil {
			t.Fatal(err)
		}

		if !testutil.DeepEqual(c.consensusResult, c.wantConsensusResult) {
			t.Fatalf("test case %d: %s, the consensusResult got: %v, want: %v", i, c.desc, c.consensusResult, c.wantConsensusResult)
		}
	}
}
