package v1

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"github.com/tendermint/tendermint/libs/log"
	"github.com/tendermint/tendermint/p2p"
	"github.com/tendermint/tendermint/types"
)

type testPeer struct {
	id     p2p.ID
	height int64
}

type testBcR struct {
	logger log.Logger
}

type testValues struct {
	numRequestsSent int
}

var testResults testValues

func resetPoolTestResults() {
	testResults.numRequestsSent = 0
}

func (testR *testBcR) sendPeerError(err error, peerID p2p.ID) {
}

func (testR *testBcR) sendStatusRequest() {
}

func (testR *testBcR) sendBlockRequest(peerID p2p.ID, height int64) error {
	testResults.numRequestsSent++
	return nil
}

func (testR *testBcR) resetStateTimer(name string, timer **time.Timer, timeout time.Duration) {
}

func (testR *testBcR) switchToConsensus() {

}

func newTestBcR() *testBcR {
	testBcR := &testBcR{logger: log.TestingLogger()}
	return testBcR
}

type tPBlocks struct {
	id     p2p.ID
	create bool
}

// Makes a block pool with specified current height, list of peers, block requests and block responses
func makeBlockPool(bcr *testBcR, height int64, peers []BpPeer, blocks map[int64]tPBlocks) *BlockPool {
	bPool := NewBlockPool(height, bcr)
	bPool.SetLogger(bcr.logger)

	txs := []types.Tx{types.Tx("foo"), types.Tx("bar")}

	var maxH int64
	for _, p := range peers {
		if p.Height > maxH {
			maxH = p.Height
		}
		bPool.peers[p.ID] = NewBpPeer(p.ID, p.Height, bcr.sendPeerError, nil)
		bPool.peers[p.ID].SetLogger(bcr.logger)

	}
	bPool.MaxPeerHeight = maxH
	for h, p := range blocks {
		bPool.blocks[h] = p.id
		bPool.peers[p.id].RequestSent(h)
		if p.create {
			// simulate that a block at height h has been received
			_ = bPool.peers[p.id].AddBlock(types.MakeBlock(h, txs, nil, nil), 100)
		}
	}
	return bPool
}

func assertPeerSetsEquivalent(t *testing.T, set1 map[p2p.ID]*BpPeer, set2 map[p2p.ID]*BpPeer) {
	assert.Equal(t, len(set1), len(set2))
	for peerID, peer1 := range set1 {
		peer2 := set2[peerID]
		assert.NotNil(t, peer2)
		assert.Equal(t, peer1.NumPendingBlockRequests, peer2.NumPendingBlockRequests)
		assert.Equal(t, peer1.Height, peer2.Height)
		assert.Equal(t, len(peer1.blocks), len(peer2.blocks))
		for h, block1 := range peer1.blocks {
			block2 := peer2.blocks[h]
			// block1 and block2 could be nil if a request was made but no block was received
			assert.Equal(t, block1, block2)
		}
	}
}

func assertBlockPoolEquivalent(t *testing.T, poolWanted, pool *BlockPool) {
	assert.Equal(t, poolWanted.blocks, pool.blocks)
	assertPeerSetsEquivalent(t, poolWanted.peers, pool.peers)
	assert.Equal(t, poolWanted.MaxPeerHeight, pool.MaxPeerHeight)
	assert.Equal(t, poolWanted.Height, pool.Height)

}

func TestBlockPoolUpdatePeer(t *testing.T) {
	testBcR := newTestBcR()

	tests := []struct {
		name       string
		pool       *BlockPool
		args       testPeer
		poolWanted *BlockPool
		errWanted  error
	}{
		{
			name:       "add a first short peer",
			pool:       makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
			args:       testPeer{"P1", 50},
			errWanted:  errPeerTooShort,
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
		},
		{
			name:       "add a first good peer",
			pool:       makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
			args:       testPeer{"P1", 101},
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 101}}, map[int64]tPBlocks{}),
		},
		{
			name:       "increase the height of P1 from 120 to 123",
			pool:       makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}}, map[int64]tPBlocks{}),
			args:       testPeer{"P1", 123},
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 123}}, map[int64]tPBlocks{}),
		},
		{
			name:       "decrease the height of P1 from 120 to 110",
			pool:       makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}}, map[int64]tPBlocks{}),
			args:       testPeer{"P1", 110},
			errWanted:  errPeerLowersItsHeight,
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
		},
		{
			name: "decrease the height of P1 from 105 to 102 with blocks",
			pool: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 105}},
				map[int64]tPBlocks{
					100: {"P1", true}, 101: {"P1", true}, 102: {"P1", true}}),
			args:      testPeer{"P1", 102},
			errWanted: errPeerLowersItsHeight,
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{},
				map[int64]tPBlocks{}),
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			pool := tt.pool
			err := pool.UpdatePeer(tt.args.id, tt.args.height)
			assert.Equal(t, tt.errWanted, err)
			assert.Equal(t, tt.poolWanted.blocks, tt.pool.blocks)
			assertPeerSetsEquivalent(t, tt.poolWanted.peers, tt.pool.peers)
			assert.Equal(t, tt.poolWanted.MaxPeerHeight, tt.pool.MaxPeerHeight)
		})
	}
}

func TestBlockPoolRemovePeer(t *testing.T) {
	testBcR := newTestBcR()

	type args struct {
		peerID p2p.ID
		err    error
	}

	tests := []struct {
		name       string
		pool       *BlockPool
		args       args
		poolWanted *BlockPool
	}{
		{
			name:       "attempt to delete non-existing peer",
			pool:       makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}}, map[int64]tPBlocks{}),
			args:       args{"P99", nil},
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}}, map[int64]tPBlocks{}),
		},
		{
			name:       "delete the only peer without blocks",
			pool:       makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}}, map[int64]tPBlocks{}),
			args:       args{"P1", nil},
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
		},
		{
			name:       "delete the shortest of two peers without blocks",
			pool:       makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 120}}, map[int64]tPBlocks{}),
			args:       args{"P1", nil},
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P2", Height: 120}}, map[int64]tPBlocks{}),
		},
		{
			name:       "delete the tallest of two peers without blocks",
			pool:       makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 120}}, map[int64]tPBlocks{}),
			args:       args{"P2", nil},
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 100}}, map[int64]tPBlocks{}),
		},
		{
			name: "delete the only peer with block requests sent and blocks received",
			pool: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}},
				map[int64]tPBlocks{100: {"P1", true}, 101: {"P1", false}}),
			args:       args{"P1", nil},
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
		},
		{
			name: "delete the shortest of two peers with block requests sent and blocks received",
			pool: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}, {ID: "P2", Height: 200}},
				map[int64]tPBlocks{100: {"P1", true}, 101: {"P1", false}}),
			args:       args{"P1", nil},
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P2", Height: 200}}, map[int64]tPBlocks{}),
		},
		{
			name: "delete the tallest of two peers with block requests sent and blocks received",
			pool: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}, {ID: "P2", Height: 110}},
				map[int64]tPBlocks{100: {"P1", true}, 101: {"P1", false}}),
			args:       args{"P1", nil},
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P2", Height: 110}}, map[int64]tPBlocks{}),
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			tt.pool.RemovePeer(tt.args.peerID, tt.args.err)
			assertBlockPoolEquivalent(t, tt.poolWanted, tt.pool)
		})
	}
}

func TestBlockPoolRemoveShortPeers(t *testing.T) {
	testBcR := newTestBcR()

	tests := []struct {
		name       string
		pool       *BlockPool
		poolWanted *BlockPool
	}{
		{
			name: "no short peers",
			pool: makeBlockPool(testBcR, 100,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 110}, {ID: "P3", Height: 120}}, map[int64]tPBlocks{}),
			poolWanted: makeBlockPool(testBcR, 100,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 110}, {ID: "P3", Height: 120}}, map[int64]tPBlocks{}),
		},

		{
			name: "one short peer",
			pool: makeBlockPool(testBcR, 100,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 90}, {ID: "P3", Height: 120}}, map[int64]tPBlocks{}),
			poolWanted: makeBlockPool(testBcR, 100,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P3", Height: 120}}, map[int64]tPBlocks{}),
		},

		{
			name: "all short peers",
			pool: makeBlockPool(testBcR, 100,
				[]BpPeer{{ID: "P1", Height: 90}, {ID: "P2", Height: 91}, {ID: "P3", Height: 92}}, map[int64]tPBlocks{}),
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			pool := tt.pool
			pool.removeShortPeers()
			assertBlockPoolEquivalent(t, tt.poolWanted, tt.pool)
		})
	}
}

func TestBlockPoolSendRequestBatch(t *testing.T) {
	type testPeerResult struct {
		id                      p2p.ID
		numPendingBlockRequests int
	}

	testBcR := newTestBcR()

	tests := []struct {
		name                       string
		pool                       *BlockPool
		maxRequestsPerPeer         int
		expRequests                map[int64]bool
		expPeerResults             []testPeerResult
		expnumPendingBlockRequests int
	}{
		{
			name:                       "one peer - send up to maxRequestsPerPeer block requests",
			pool:                       makeBlockPool(testBcR, 10, []BpPeer{{ID: "P1", Height: 100}}, map[int64]tPBlocks{}),
			maxRequestsPerPeer:         2,
			expRequests:                map[int64]bool{10: true, 11: true},
			expPeerResults:             []testPeerResult{{id: "P1", numPendingBlockRequests: 2}},
			expnumPendingBlockRequests: 2,
		},
		{
			name:               "n peers - send n*maxRequestsPerPeer block requests",
			pool:               makeBlockPool(testBcR, 10, []BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}}, map[int64]tPBlocks{}),
			maxRequestsPerPeer: 2,
			expRequests:        map[int64]bool{10: true, 11: true},
			expPeerResults: []testPeerResult{
				{id: "P1", numPendingBlockRequests: 2},
				{id: "P2", numPendingBlockRequests: 2}},
			expnumPendingBlockRequests: 4,
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			resetPoolTestResults()

			var pool = tt.pool
			maxRequestsPerPeer = tt.maxRequestsPerPeer
			pool.MakeNextRequests(10)
			assert.Equal(t, testResults.numRequestsSent, maxRequestsPerPeer*len(pool.peers))

			for _, tPeer := range tt.expPeerResults {
				var peer = pool.peers[tPeer.id]
				assert.NotNil(t, peer)
				assert.Equal(t, tPeer.numPendingBlockRequests, peer.NumPendingBlockRequests)
			}
			assert.Equal(t, testResults.numRequestsSent, maxRequestsPerPeer*len(pool.peers))

		})
	}
}

func TestBlockPoolAddBlock(t *testing.T) {
	testBcR := newTestBcR()
	txs := []types.Tx{types.Tx("foo"), types.Tx("bar")}

	type args struct {
		peerID    p2p.ID
		block     *types.Block
		blockSize int
	}
	tests := []struct {
		name       string
		pool       *BlockPool
		args       args
		poolWanted *BlockPool
		errWanted  error
	}{
		{name: "block from unknown peer",
			pool: makeBlockPool(testBcR, 10, []BpPeer{{ID: "P1", Height: 100}}, map[int64]tPBlocks{}),
			args: args{
				peerID:    "P2",
				block:     types.MakeBlock(int64(10), txs, nil, nil),
				blockSize: 100,
			},
			poolWanted: makeBlockPool(testBcR, 10, []BpPeer{{ID: "P1", Height: 100}}, map[int64]tPBlocks{}),
			errWanted:  errBadDataFromPeer,
		},
		{name: "unexpected block 11 from known peer - waiting for 10",
			pool: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}},
				map[int64]tPBlocks{10: {"P1", false}}),
			args: args{
				peerID:    "P1",
				block:     types.MakeBlock(int64(11), txs, nil, nil),
				blockSize: 100,
			},
			poolWanted: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}},
				map[int64]tPBlocks{10: {"P1", false}}),
			errWanted: errMissingBlock,
		},
		{name: "unexpected block 10 from known peer - already have 10",
			pool: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}},
				map[int64]tPBlocks{10: {"P1", true}, 11: {"P1", false}}),
			args: args{
				peerID:    "P1",
				block:     types.MakeBlock(int64(10), txs, nil, nil),
				blockSize: 100,
			},
			poolWanted: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}},
				map[int64]tPBlocks{10: {"P1", true}, 11: {"P1", false}}),
			errWanted: errDuplicateBlock,
		},
		{name: "unexpected block 10 from known peer P2 - expected 10 to come from P1",
			pool: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}},
				map[int64]tPBlocks{10: {"P1", false}}),
			args: args{
				peerID:    "P2",
				block:     types.MakeBlock(int64(10), txs, nil, nil),
				blockSize: 100,
			},
			poolWanted: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}},
				map[int64]tPBlocks{10: {"P1", false}}),
			errWanted: errBadDataFromPeer,
		},
		{name: "expected block from known peer",
			pool: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}},
				map[int64]tPBlocks{10: {"P1", false}}),
			args: args{
				peerID:    "P1",
				block:     types.MakeBlock(int64(10), txs, nil, nil),
				blockSize: 100,
			},
			poolWanted: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}},
				map[int64]tPBlocks{10: {"P1", true}}),
			errWanted: nil,
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			err := tt.pool.AddBlock(tt.args.peerID, tt.args.block, tt.args.blockSize)
			assert.Equal(t, tt.errWanted, err)
			assertBlockPoolEquivalent(t, tt.poolWanted, tt.pool)
		})
	}
}

func TestBlockPoolFirstTwoBlocksAndPeers(t *testing.T) {
	testBcR := newTestBcR()

	tests := []struct {
		name         string
		pool         *BlockPool
		firstWanted  int64
		secondWanted int64
		errWanted    error
	}{
		{
			name: "both blocks missing",
			pool: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}},
				map[int64]tPBlocks{15: {"P1", true}, 16: {"P2", true}}),
			errWanted: errMissingBlock,
		},
		{
			name: "second block missing",
			pool: makeBlockPool(testBcR, 15,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}},
				map[int64]tPBlocks{15: {"P1", true}, 18: {"P2", true}}),
			firstWanted: 15,
			errWanted:   errMissingBlock,
		},
		{
			name: "first block missing",
			pool: makeBlockPool(testBcR, 15,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}},
				map[int64]tPBlocks{16: {"P2", true}, 18: {"P2", true}}),
			secondWanted: 16,
			errWanted:    errMissingBlock,
		},
		{
			name: "both blocks present",
			pool: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}},
				map[int64]tPBlocks{10: {"P1", true}, 11: {"P2", true}}),
			firstWanted:  10,
			secondWanted: 11,
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			pool := tt.pool
			gotFirst, gotSecond, err := pool.FirstTwoBlocksAndPeers()
			assert.Equal(t, tt.errWanted, err)

			if tt.firstWanted != 0 {
				peer := pool.blocks[tt.firstWanted]
				block := pool.peers[peer].blocks[tt.firstWanted]
				assert.Equal(t, block, gotFirst.block,
					"BlockPool.FirstTwoBlocksAndPeers() gotFirst = %v, want %v",
					tt.firstWanted, gotFirst.block.Height)
			}

			if tt.secondWanted != 0 {
				peer := pool.blocks[tt.secondWanted]
				block := pool.peers[peer].blocks[tt.secondWanted]
				assert.Equal(t, block, gotSecond.block,
					"BlockPool.FirstTwoBlocksAndPeers() gotFirst = %v, want %v",
					tt.secondWanted, gotSecond.block.Height)
			}
		})
	}
}

func TestBlockPoolInvalidateFirstTwoBlocks(t *testing.T) {
	testBcR := newTestBcR()

	tests := []struct {
		name       string
		pool       *BlockPool
		poolWanted *BlockPool
	}{
		{
			name: "both blocks missing",
			pool: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}},
				map[int64]tPBlocks{15: {"P1", true}, 16: {"P2", true}}),
			poolWanted: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}},
				map[int64]tPBlocks{15: {"P1", true}, 16: {"P2", true}}),
		},
		{
			name: "second block missing",
			pool: makeBlockPool(testBcR, 15,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}},
				map[int64]tPBlocks{15: {"P1", true}, 18: {"P2", true}}),
			poolWanted: makeBlockPool(testBcR, 15,
				[]BpPeer{{ID: "P2", Height: 100}},
				map[int64]tPBlocks{18: {"P2", true}}),
		},
		{
			name: "first block missing",
			pool: makeBlockPool(testBcR, 15,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}},
				map[int64]tPBlocks{18: {"P1", true}, 16: {"P2", true}}),
			poolWanted: makeBlockPool(testBcR, 15,
				[]BpPeer{{ID: "P1", Height: 100}},
				map[int64]tPBlocks{18: {"P1", true}}),
		},
		{
			name: "both blocks present",
			pool: makeBlockPool(testBcR, 10,
				[]BpPeer{{ID: "P1", Height: 100}, {ID: "P2", Height: 100}},
				map[int64]tPBlocks{10: {"P1", true}, 11: {"P2", true}}),
			poolWanted: makeBlockPool(testBcR, 10,
				[]BpPeer{},
				map[int64]tPBlocks{}),
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			tt.pool.InvalidateFirstTwoBlocks(errNoPeerResponse)
			assertBlockPoolEquivalent(t, tt.poolWanted, tt.pool)
		})
	}
}

func TestProcessedCurrentHeightBlock(t *testing.T) {
	testBcR := newTestBcR()

	tests := []struct {
		name       string
		pool       *BlockPool
		poolWanted *BlockPool
	}{
		{
			name: "one peer",
			pool: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}},
				map[int64]tPBlocks{100: {"P1", true}, 101: {"P1", true}}),
			poolWanted: makeBlockPool(testBcR, 101, []BpPeer{{ID: "P1", Height: 120}},
				map[int64]tPBlocks{101: {"P1", true}}),
		},
		{
			name: "multiple peers",
			pool: makeBlockPool(testBcR, 100,
				[]BpPeer{{ID: "P1", Height: 120}, {ID: "P2", Height: 120}, {ID: "P3", Height: 130}},
				map[int64]tPBlocks{
					100: {"P1", true}, 104: {"P1", true}, 105: {"P1", false},
					101: {"P2", true}, 103: {"P2", false},
					102: {"P3", true}, 106: {"P3", true}}),
			poolWanted: makeBlockPool(testBcR, 101,
				[]BpPeer{{ID: "P1", Height: 120}, {ID: "P2", Height: 120}, {ID: "P3", Height: 130}},
				map[int64]tPBlocks{
					104: {"P1", true}, 105: {"P1", false},
					101: {"P2", true}, 103: {"P2", false},
					102: {"P3", true}, 106: {"P3", true}}),
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			tt.pool.ProcessedCurrentHeightBlock()
			assertBlockPoolEquivalent(t, tt.poolWanted, tt.pool)
		})
	}
}

func TestRemovePeerAtCurrentHeight(t *testing.T) {
	testBcR := newTestBcR()

	tests := []struct {
		name       string
		pool       *BlockPool
		poolWanted *BlockPool
	}{
		{
			name: "one peer, remove peer for block at H",
			pool: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}},
				map[int64]tPBlocks{100: {"P1", false}, 101: {"P1", true}}),
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
		},
		{
			name: "one peer, remove peer for block at H+1",
			pool: makeBlockPool(testBcR, 100, []BpPeer{{ID: "P1", Height: 120}},
				map[int64]tPBlocks{100: {"P1", true}, 101: {"P1", false}}),
			poolWanted: makeBlockPool(testBcR, 100, []BpPeer{}, map[int64]tPBlocks{}),
		},
		{
			name: "multiple peers, remove peer for block at H",
			pool: makeBlockPool(testBcR, 100,
				[]BpPeer{{ID: "P1", Height: 120}, {ID: "P2", Height: 120}, {ID: "P3", Height: 130}},
				map[int64]tPBlocks{
					100: {"P1", false}, 104: {"P1", true}, 105: {"P1", false},
					101: {"P2", true}, 103: {"P2", false},
					102: {"P3", true}, 106: {"P3", true}}),
			poolWanted: makeBlockPool(testBcR, 100,
				[]BpPeer{{ID: "P2", Height: 120}, {ID: "P3", Height: 130}},
				map[int64]tPBlocks{
					101: {"P2", true}, 103: {"P2", false},
					102: {"P3", true}, 106: {"P3", true}}),
		},
		{
			name: "multiple peers, remove peer for block at H+1",
			pool: makeBlockPool(testBcR, 100,
				[]BpPeer{{ID: "P1", Height: 120}, {ID: "P2", Height: 120}, {ID: "P3", Height: 130}},
				map[int64]tPBlocks{
					100: {"P1", true}, 104: {"P1", true}, 105: {"P1", false},
					101: {"P2", false}, 103: {"P2", false},
					102: {"P3", true}, 106: {"P3", true}}),
			poolWanted: makeBlockPool(testBcR, 100,
				[]BpPeer{{ID: "P1", Height: 120}, {ID: "P3", Height: 130}},
				map[int64]tPBlocks{
					100: {"P1", true}, 104: {"P1", true}, 105: {"P1", false},
					102: {"P3", true}, 106: {"P3", true}}),
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			tt.pool.RemovePeerAtCurrentHeights(errNoPeerResponse)
			assertBlockPoolEquivalent(t, tt.poolWanted, tt.pool)
		})
	}
}
