package protocol

import (
	"testing"
	"time"

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

var testBlocks = []*types.Block{
	&types.Block{BlockHeader: types.BlockHeader{
		PreviousBlockHash: bc.Hash{V0: 1},
		Timestamp:         0,
	}},
	&types.Block{BlockHeader: types.BlockHeader{
		PreviousBlockHash: bc.Hash{V0: 1},
		Timestamp:         1,
	}},
	&types.Block{BlockHeader: types.BlockHeader{
		PreviousBlockHash: bc.Hash{V0: 2},
		Timestamp:         3,
	}},
}

var blockHashes = []bc.Hash{}

func init() {
	for _, block := range testBlocks {
		blockHashes = append(blockHashes, block.Hash())
	}
}

func TestDeleteLRU(t *testing.T) {
	now := time.Now()
	cases := []struct {
		before *OrphanManage
		after  *OrphanManage
	}{
		{
			before: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], now},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			after: &OrphanManage{
				orphan:      map[bc.Hash]*orphanBlock{},
				prevOrphans: map[bc.Hash][]*bc.Hash{},
			},
		},
		{
			before: &OrphanManage{
				orphan:      map[bc.Hash]*orphanBlock{},
				prevOrphans: map[bc.Hash][]*bc.Hash{},
			},
			after: &OrphanManage{
				orphan:      map[bc.Hash]*orphanBlock{},
				prevOrphans: map[bc.Hash][]*bc.Hash{},
			},
		},
		{
			before: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], now.Add(2)},
					blockHashes[1]: &orphanBlock{testBlocks[1], now.Add(1)},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0], &blockHashes[1]},
				},
			},
			after: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], now.Add(2)},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
		},
	}

	for i, c := range cases {
		c.before.deleteLRU()
		if !testutil.DeepEqual(c.before, c.after) {
			t.Errorf("case %d: got %v want %v", i, c.before, c.after)
		}
	}
}

func TestOrphanManageAdd(t *testing.T) {
	cases := []struct {
		before    *OrphanManage
		after     *OrphanManage
		addOrphan *types.Block
	}{
		{
			before: &OrphanManage{
				orphan:      map[bc.Hash]*orphanBlock{},
				prevOrphans: map[bc.Hash][]*bc.Hash{},
			},
			after: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			addOrphan: testBlocks[0],
		},
		{
			before: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			after: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			addOrphan: testBlocks[0],
		},
		{
			before: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			after: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
					blockHashes[1]: &orphanBlock{testBlocks[1], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0], &blockHashes[1]},
				},
			},
			addOrphan: testBlocks[1],
		},
		{
			before: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			after: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
					blockHashes[2]: &orphanBlock{testBlocks[2], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
					bc.Hash{V0: 2}: []*bc.Hash{&blockHashes[2]},
				},
			},
			addOrphan: testBlocks[2],
		},
	}

	for i, c := range cases {
		c.before.Add(c.addOrphan)
		for _, orphan := range c.before.orphan {
			orphan.expiration = time.Time{}
		}
		if !testutil.DeepEqual(c.before, c.after) {
			t.Errorf("case %d: got %v want %v", i, c.before, c.after)
		}
	}
}

func TestOrphanManageDelete(t *testing.T) {
	cases := []struct {
		before *OrphanManage
		after  *OrphanManage
		remove *bc.Hash
	}{
		{
			before: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			after: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			remove: &blockHashes[1],
		},
		{
			before: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			after: &OrphanManage{
				orphan:      map[bc.Hash]*orphanBlock{},
				prevOrphans: map[bc.Hash][]*bc.Hash{},
			},
			remove: &blockHashes[0],
		},
		{
			before: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
					blockHashes[1]: &orphanBlock{testBlocks[1], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0], &blockHashes[1]},
				},
			},
			after: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{testBlocks[0], time.Time{}},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			remove: &blockHashes[1],
		},
	}

	for i, c := range cases {
		c.before.delete(c.remove)
		if !testutil.DeepEqual(c.before, c.after) {
			t.Errorf("case %d: got %v want %v", i, c.before, c.after)
		}
	}
}

func TestOrphanManageExpire(t *testing.T) {
	cases := []struct {
		before *OrphanManage
		after  *OrphanManage
	}{
		{
			before: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{
						testBlocks[0],
						time.Unix(1633479700, 0),
					},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			after: &OrphanManage{
				orphan:      map[bc.Hash]*orphanBlock{},
				prevOrphans: map[bc.Hash][]*bc.Hash{},
			},
		},
		{
			before: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{
						testBlocks[0],
						time.Unix(1633479702, 0),
					},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
			after: &OrphanManage{
				orphan: map[bc.Hash]*orphanBlock{
					blockHashes[0]: &orphanBlock{
						testBlocks[0],
						time.Unix(1633479702, 0),
					},
				},
				prevOrphans: map[bc.Hash][]*bc.Hash{
					bc.Hash{V0: 1}: []*bc.Hash{&blockHashes[0]},
				},
			},
		},
	}

	for i, c := range cases {
		c.before.orphanExpire(time.Unix(1633479701, 0))
		if !testutil.DeepEqual(c.before, c.after) {
			t.Errorf("case %d: got %v want %v", i, c.before, c.after)
		}
	}
}

func TestOrphanManageNumLimit(t *testing.T) {
	cases := []struct {
		addOrphanBlockNum    int
		expectOrphanBlockNum int
	}{
		{
			addOrphanBlockNum:    10,
			expectOrphanBlockNum: 10,
		},
		{
			addOrphanBlockNum:    numOrphanBlockLimit,
			expectOrphanBlockNum: numOrphanBlockLimit,
		},
		{
			addOrphanBlockNum:    numOrphanBlockLimit + 1,
			expectOrphanBlockNum: numOrphanBlockLimit,
		},
		{
			addOrphanBlockNum:    numOrphanBlockLimit + 10,
			expectOrphanBlockNum: numOrphanBlockLimit,
		},
	}

	for i, c := range cases {
		orphanManage := &OrphanManage{
			orphan:      map[bc.Hash]*orphanBlock{},
			prevOrphans: map[bc.Hash][]*bc.Hash{},
		}
		for num := 0; num < c.addOrphanBlockNum; num++ {
			orphanManage.Add(&types.Block{BlockHeader: types.BlockHeader{Height: uint64(num)}})
		}
		if len(orphanManage.orphan) != c.expectOrphanBlockNum {
			t.Errorf("case %d: got %d want %d", i, len(orphanManage.orphan), c.expectOrphanBlockNum)
		}
	}
}
