package mps

import (
	"math/big"
	"sync"
	"testing"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/privatecache"
	"github.com/ethereum/go-ethereum/core/rawdb"
	"github.com/ethereum/go-ethereum/core/state"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/stretchr/testify/assert"
)

// TestMultiplePSRCopy tests that copying a the PSR object indeed makes the original and
// the copy and their corresponding managed states independent of each other.
func TestMultiplePSRCopy(t *testing.T) {
	testdb := rawdb.NewMemoryDatabase()
	testCache := state.NewDatabase(testdb)
	privateCacheProvider := privatecache.NewPrivateCacheProvider(testdb, nil, testCache, false)
	psr, _ := NewMultiplePrivateStateRepository(testdb, testCache, common.Hash{}, privateCacheProvider)

	testState, _ := psr.StatePSI(types.PrivateStateIdentifier("test"))
	privState, _ := psr.StatePSI(types.DefaultPrivateStateIdentifier)

	for i := byte(0); i < 255; i++ {
		addr := common.BytesToAddress([]byte{i})
		testState.AddBalance(addr, big.NewInt(int64(i)))
	}
	testState.Finalise(false)

	for i := byte(0); i < 255; i++ {
		addr := common.BytesToAddress([]byte{i})
		privState.AddBalance(addr, big.NewInt(int64(i)))
	}
	privState.Finalise(false)

	psrCopy := psr.Copy().(*MultiplePrivateStateRepository)

	testStateCopy, _ := psrCopy.StatePSI(types.PrivateStateIdentifier("test"))
	privStateCopy, _ := psrCopy.StatePSI(types.DefaultPrivateStateIdentifier)
	addedState, _ := psrCopy.StatePSI(types.PrivateStateIdentifier("added"))

	// modify all in memory
	for i := byte(0); i < 255; i++ {
		testState.AddBalance(common.BytesToAddress([]byte{i}), big.NewInt(2*int64(i)))
		privState.AddBalance(common.BytesToAddress([]byte{i}), big.NewInt(2*int64(i)))

		testStateCopy.AddBalance(common.BytesToAddress([]byte{i}), big.NewInt(3*int64(i)))
		privStateCopy.AddBalance(common.BytesToAddress([]byte{i}), big.NewInt(3*int64(i)))
		addedState.AddBalance(common.BytesToAddress([]byte{i}), big.NewInt(3*int64(i)))
	}

	// Finalise the changes on all concurrently
	finalise := func(wg *sync.WaitGroup, db *state.StateDB) {
		defer wg.Done()
		db.Finalise(true)
	}

	var wg sync.WaitGroup
	wg.Add(5)
	go finalise(&wg, testState)
	go finalise(&wg, testStateCopy)
	go finalise(&wg, privState)
	go finalise(&wg, privStateCopy)
	go finalise(&wg, addedState)
	wg.Wait()

	//copies contain correct managed states
	assert.Contains(t, psr.managedStates, types.EmptyPrivateStateIdentifier)
	assert.Contains(t, psr.managedStates, types.DefaultPrivateStateIdentifier)
	assert.Contains(t, psr.managedStates, types.PrivateStateIdentifier("test"))
	assert.NotContains(t, psr.managedStates, types.PrivateStateIdentifier("added"))

	assert.Contains(t, psrCopy.managedStates, types.EmptyPrivateStateIdentifier)
	assert.Contains(t, psrCopy.managedStates, types.DefaultPrivateStateIdentifier)
	assert.Contains(t, psrCopy.managedStates, types.PrivateStateIdentifier("test"))
	assert.Contains(t, psrCopy.managedStates, types.PrivateStateIdentifier("added"))

	assert.Equal(t, psr.db, psrCopy.db)
	assert.Equal(t, psr.repoCache, psrCopy.repoCache)
	assert.NotEqual(t, psr.trie, psrCopy.trie)

	// Verify that the all states have been updated independently
	for i := byte(0); i < 255; i++ {
		addr := common.BytesToAddress([]byte{i})
		testObj := testState.GetOrNewStateObject(addr)
		testCopyObj := testStateCopy.GetOrNewStateObject(addr)
		privObj := privState.GetOrNewStateObject(addr)
		privCopyObj := privStateCopy.GetOrNewStateObject(addr)
		addedObj := addedState.GetOrNewStateObject(addr)

		if want := big.NewInt(3 * int64(i)); testObj.Balance().Cmp(want) != 0 {
			t.Errorf("empty obj %d: balance mismatch: have %v, want %v", i, testObj.Balance(), want)
		}
		if want := big.NewInt(3 * int64(i)); privObj.Balance().Cmp(want) != 0 {
			t.Errorf("priv obj %d: balance mismatch: have %v, want %v", i, privObj.Balance(), want)
		}
		if want := big.NewInt(4 * int64(i)); testCopyObj.Balance().Cmp(want) != 0 {
			t.Errorf("empty copy obj %d: balance mismatch: have %v, want %v", i, testCopyObj.Balance(), want)
		}
		if want := big.NewInt(4 * int64(i)); privCopyObj.Balance().Cmp(want) != 0 {
			t.Errorf("priv copy obj %d: balance mismatch: have %v, want %v", i, privCopyObj.Balance(), want)
		}
		if want := big.NewInt(3 * int64(i)); addedObj.Balance().Cmp(want) != 0 {
			t.Errorf("added obj %d: balance mismatch: have %v, want %v", i, addedObj.Balance(), want)
		}
	}
}

// TestMultiplePSRReset tests that state objects are cleared from all managedState statedbs after reset call
// Any updated stateObjects not committed to statedbs before reset will be cleared
func TestMultiplePSRReset(t *testing.T) {
	testdb := rawdb.NewMemoryDatabase()
	testCache := state.NewDatabase(testdb)
	privateCacheProvider := privatecache.NewPrivateCacheProvider(testdb, nil, testCache, false)
	psr, _ := NewMultiplePrivateStateRepository(testdb, testCache, common.Hash{}, privateCacheProvider)

	testState, _ := psr.StatePSI(types.PrivateStateIdentifier("test"))
	emptyState, _ := psr.StatePSI(types.EmptyPrivateStateIdentifier)

	addr := common.BytesToAddress([]byte{254})
	testState.AddBalance(addr, big.NewInt(int64(254)))
	emptyState.AddBalance(addr, big.NewInt(int64(254)))

	// have something to revert to (rather than the empty trie of private states)
	psr.CommitAndWrite(false, types.NewBlockWithHeader(&types.Header{Root: common.Hash{}}))

	// testState2 should branch from the emptyState - so it should contain the contract with address 254...
	testState2, _ := psr.StatePSI(types.PrivateStateIdentifier("test2"))

	for i := byte(0); i < 254; i++ {
		addr := common.BytesToAddress([]byte{i})
		testState.AddBalance(addr, big.NewInt(int64(i)))
		testState2.AddBalance(addr, big.NewInt(int64(i)))
		emptyState.AddBalance(addr, big.NewInt(int64(i)))
	}
	testState.Finalise(false)
	testState2.Finalise(false)
	emptyState.Finalise(false)

	for i := byte(0); i < 255; i++ {
		addr := common.BytesToAddress([]byte{i})
		assert.True(t, testState.Exist(addr))
		assert.True(t, testState2.Exist(addr))
		assert.True(t, emptyState.Exist(addr))
	}

	psr.Reset()

	// test2 is no longer there in the managed states after reset
	assert.Contains(t, psr.managedStates, types.PrivateStateIdentifier("test"))
	assert.NotContains(t, psr.managedStates, types.PrivateStateIdentifier("test2"))
	assert.Contains(t, psr.managedStates, types.EmptyPrivateStateIdentifier)

	for i := byte(0); i < 254; i++ {
		addr := common.BytesToAddress([]byte{i})
		assert.False(t, testState.Exist(addr))
		assert.False(t, emptyState.Exist(addr))
	}
	addr = common.BytesToAddress([]byte{254})
	assert.True(t, testState.Exist(addr))
	assert.True(t, emptyState.Exist(addr))
}

// TestCreatingManagedStates tests that managed states are created and added to managedState map
func TestCreatingManagedStates(t *testing.T) {
	testdb := rawdb.NewMemoryDatabase()
	testCache := state.NewDatabase(testdb)
	privateCacheProvider := privatecache.NewPrivateCacheProvider(testdb, nil, testCache, false)
	psr, _ := NewMultiplePrivateStateRepository(testdb, testCache, common.Hash{}, privateCacheProvider)

	//create some managed states
	psr.DefaultState()
	psr.StatePSI(types.PrivateStateIdentifier("test"))
	psr.StatePSI(types.DefaultPrivateStateIdentifier)

	//check if they exist in managedStates map
	assert.Contains(t, psr.managedStates, types.EmptyPrivateStateIdentifier)
	assert.Contains(t, psr.managedStates, types.DefaultPrivateStateIdentifier)
	assert.Contains(t, psr.managedStates, types.PrivateStateIdentifier("test"))
	assert.NotContains(t, psr.managedStates, types.PrivateStateIdentifier("added"))
}

// TestMultiplePSRCommit tests that managedStates are updated, trie of states is updated but not written to db
func TestMultiplePSRCommit(t *testing.T) {
	testdb := rawdb.NewMemoryDatabase()
	testCache := state.NewDatabase(testdb)
	privateCacheProvider := privatecache.NewPrivateCacheProvider(testdb, nil, testCache, false)
	psr, _ := NewMultiplePrivateStateRepository(testdb, testCache, common.Hash{}, privateCacheProvider)
	header := &types.Header{Number: big.NewInt(int64(1)), Root: common.Hash{123}}
	block := types.NewBlockWithHeader(header)

	testState, _ := psr.StatePSI(types.PrivateStateIdentifier("test"))
	privState, _ := psr.StatePSI(types.DefaultPrivateStateIdentifier)

	//states have empty tries first
	testRoot := testState.IntermediateRoot(false)
	privRoot := privState.IntermediateRoot(false)
	assert.Equal(t, testRoot, emptyRoot)
	assert.Equal(t, privRoot, emptyRoot)

	//make updates to states
	for i := byte(0); i < 255; i++ {
		addr := common.BytesToAddress([]byte{i})
		testState.AddBalance(addr, big.NewInt(int64(i)))
		privState.AddBalance(addr, big.NewInt(int64(i)))
	}

	assert.Equal(t, rawdb.GetPrivateStatesTrieRoot(testdb, block.Root()), common.Hash{})

	psr.Commit(false, block)

	//trie root updated but not committed to db
	assert.NotEqual(t, psr.trie.Hash(), common.Hash{})
	assert.Equal(t, rawdb.GetPrivateStatesTrieRoot(testdb, block.Root()), common.Hash{})

	privateKey, _ := psr.trie.TryGet([]byte(types.DefaultPrivateStateIdentifier))
	assert.NotEqual(t, len(privateKey), 0)
	testKey, _ := psr.trie.TryGet([]byte(types.PrivateStateIdentifier("test")))
	assert.NotEqual(t, len(testKey), 0)
	emptyKey, _ := psr.trie.TryGet([]byte(types.EmptyPrivateStateIdentifier))
	assert.NotEqual(t, len(emptyKey), 0)
	notKey, _ := psr.trie.TryGet([]byte(types.PrivateStateIdentifier("notKey")))
	assert.Equal(t, len(notKey), 0)

	//managed state tries updated
	testRoot = testState.IntermediateRoot(false)
	privRoot = privState.IntermediateRoot(false)
	assert.NotEqual(t, testRoot, emptyRoot)
	assert.NotEqual(t, privRoot, emptyRoot)
}

// TestMultiplePSRCommitAndWrite tests that managedStates are updated, trie of states is updated and written to db
func TestMultiplePSRCommitAndWrite(t *testing.T) {
	testdb := rawdb.NewMemoryDatabase()
	testCache := state.NewDatabase(testdb)
	privateCacheProvider := privatecache.NewPrivateCacheProvider(testdb, nil, testCache, false)
	psr, _ := NewMultiplePrivateStateRepository(testdb, testCache, common.Hash{}, privateCacheProvider)
	header := &types.Header{Number: big.NewInt(int64(1)), Root: common.Hash{123}}
	block := types.NewBlockWithHeader(header)

	testState, _ := psr.StatePSI(types.PrivateStateIdentifier("test"))
	privState, _ := psr.StatePSI(types.DefaultPrivateStateIdentifier)

	//states have empty tries first
	testRoot := testState.IntermediateRoot(false)
	privRoot := privState.IntermediateRoot(false)
	assert.Equal(t, testRoot, emptyRoot)
	assert.Equal(t, privRoot, emptyRoot)

	//make updates to states
	for i := byte(0); i < 255; i++ {
		addr := common.BytesToAddress([]byte{i})
		testState.AddBalance(addr, big.NewInt(int64(i)))
		privState.AddBalance(addr, big.NewInt(int64(i)))
	}

	assert.Equal(t, rawdb.GetPrivateStatesTrieRoot(testdb, block.Root()), common.Hash{})

	psr.CommitAndWrite(false, block)

	//trie root updated and committed to db
	assert.NotEqual(t, psr.trie.Hash(), common.Hash{})
	assert.NotEqual(t, rawdb.GetPrivateStatesTrieRoot(testdb, block.Root()), common.Hash{})

	privateKey, _ := psr.trie.TryGet([]byte(types.DefaultPrivateStateIdentifier))
	assert.NotEqual(t, len(privateKey), 0)
	testKey, _ := psr.trie.TryGet([]byte(types.PrivateStateIdentifier("test")))
	assert.NotEqual(t, len(testKey), 0)
	emptyKey, _ := psr.trie.TryGet([]byte(types.EmptyPrivateStateIdentifier))
	assert.NotEqual(t, len(emptyKey), 0)
	notKey, _ := psr.trie.TryGet([]byte(types.PrivateStateIdentifier("notKey")))
	assert.Equal(t, len(notKey), 0)

	//managed state tries updated
	testRoot = testState.IntermediateRoot(false)
	privRoot = privState.IntermediateRoot(false)
	assert.NotEqual(t, testRoot, emptyRoot)
	assert.NotEqual(t, privRoot, emptyRoot)
}

// TestMultiplePSRIntroduceNewPrivateState tests that a newly introduced private state is branched from the empty state and maintained accordingly
func TestMultiplePSRIntroduceNewPrivateState(t *testing.T) {
	testPS1 := types.PrivateStateIdentifier("PS1")
	testPS2 := types.PrivateStateIdentifier("PS2")
	testdb := rawdb.NewMemoryDatabase()
	testCache := state.NewDatabase(testdb)
	privateCacheProvider := privatecache.NewPrivateCacheProvider(testdb, nil, testCache, false)
	psr, _ := NewMultiplePrivateStateRepository(testdb, testCache, common.Hash{}, privateCacheProvider)
	header1 := &types.Header{Number: big.NewInt(int64(1)), Root: common.Hash{123}}
	block1 := types.NewBlockWithHeader(header1)

	header2 := &types.Header{Number: big.NewInt(int64(2)), Root: common.Hash{124}}
	block2 := types.NewBlockWithHeader(header2)

	testState1, _ := psr.StatePSI(testPS1)
	emptyState, _ := psr.StatePSI(types.EmptyPrivateStateIdentifier)

	//states have empty tries first
	testState1Root := testState1.IntermediateRoot(false)
	emptyStateRoot := emptyState.IntermediateRoot(false)
	assert.Equal(t, testState1Root, emptyRoot)
	assert.Equal(t, emptyStateRoot, emptyRoot)

	//make updates to states
	for i := byte(0); i < 10; i++ {
		addr := common.BytesToAddress([]byte{i})
		testState1.AddBalance(addr, big.NewInt(int64(i)))
		emptyState.AddBalance(addr, big.NewInt(int64(i)))
	}

	assert.Equal(t, rawdb.GetPrivateStatesTrieRoot(testdb, block1.Root()), common.Hash{})

	psr.CommitAndWrite(false, block1)

	//trie root updated and committed to db
	psrRootHash1 := psr.trie.Hash()
	assert.NotEqual(t, psrRootHash1, emptyRoot)
	assert.Equal(t, rawdb.GetPrivateStatesTrieRoot(testdb, block1.Root()), psrRootHash1)

	emptyStateRootHash, _ := psr.trie.TryGet([]byte(types.EmptyPrivateStateIdentifier))
	assert.NotEqual(t, len(emptyStateRootHash), 0)
	ps1RootHash, _ := psr.trie.TryGet([]byte(testPS1))
	assert.NotEqual(t, len(ps1RootHash), 0)
	notKeyRootHash, _ := psr.trie.TryGet([]byte(types.PrivateStateIdentifier("notKey")))
	assert.Equal(t, len(notKeyRootHash), 0)

	//managed state tries updated
	testState1Root = testState1.IntermediateRoot(false)
	emptyStateRoot = emptyState.IntermediateRoot(false)
	assert.NotEqual(t, testState1Root, emptyRoot)
	assert.NotEqual(t, emptyStateRoot, emptyRoot)

	// begin adding state at block2
	psr, _ = NewMultiplePrivateStateRepository(testdb, testCache, rawdb.GetPrivateStatesTrieRoot(testdb, block1.Root()), privateCacheProvider)

	testState1, _ = psr.StatePSI(testPS1)
	testState2, _ := psr.StatePSI(testPS2)
	emptyState, _ = psr.StatePSI(types.EmptyPrivateStateIdentifier)

	//make updates to states
	for i := byte(10); i < 20; i++ {
		addr := common.BytesToAddress([]byte{i})
		testState1.AddBalance(addr, big.NewInt(int64(i)))
		testState2.AddBalance(addr, big.NewInt(int64(i)))
		emptyState.AddBalance(addr, big.NewInt(int64(i)))
	}

	psr.CommitAndWrite(false, block2)

	psr, _ = NewMultiplePrivateStateRepository(testdb, testCache, rawdb.GetPrivateStatesTrieRoot(testdb, block2.Root()), privateCacheProvider)

	testState1, _ = psr.StatePSI(testPS1)
	testState2, _ = psr.StatePSI(testPS2)
	emptyState, _ = psr.StatePSI(types.EmptyPrivateStateIdentifier)

	// we've only added addresses from 10 to 20 to testState2 but since it branched from emptyState it should also contain addresses from 0 to 10
	for i := byte(0); i < 20; i++ {
		addr := common.BytesToAddress([]byte{i})
		assert.True(t, testState1.Exist(addr))
		assert.True(t, testState2.Exist(addr))
		assert.True(t, emptyState.Exist(addr))
	}

	// check that PS2 does not exist in the PSR at block1 height
	psr, _ = NewMultiplePrivateStateRepository(testdb, testCache, rawdb.GetPrivateStatesTrieRoot(testdb, block1.Root()), privateCacheProvider)

	emptyStateRootHash, _ = psr.trie.TryGet([]byte(types.EmptyPrivateStateIdentifier))
	assert.NotEqual(t, len(emptyStateRootHash), 0)
	ps1RootHash, _ = psr.trie.TryGet([]byte(testPS1))
	assert.NotEqual(t, len(ps1RootHash), 0)
	ps2RootHash, _ := psr.trie.TryGet([]byte(testPS2))
	assert.Equal(t, len(ps2RootHash), 0)

	// check that PS2 does exist in the PSR at block2 height
	psr, _ = NewMultiplePrivateStateRepository(testdb, testCache, rawdb.GetPrivateStatesTrieRoot(testdb, block2.Root()), privateCacheProvider)

	emptyStateRootHash, _ = psr.trie.TryGet([]byte(types.EmptyPrivateStateIdentifier))
	assert.NotEqual(t, len(emptyStateRootHash), 0)
	ps1RootHash, _ = psr.trie.TryGet([]byte(testPS1))
	assert.NotEqual(t, len(ps1RootHash), 0)
	ps2RootHash, _ = psr.trie.TryGet([]byte(testPS2))
	assert.NotEqual(t, len(ps2RootHash), 0)
}

// TestMultiplePSRRemovalFromPrivateState tests that exist no longer picks suicided accounts
func TestMultiplePSRRemovalFromPrivateState(t *testing.T) {
	testPS1 := types.PrivateStateIdentifier("PS1")
	testdb := rawdb.NewMemoryDatabase()
	testCache := state.NewDatabase(testdb)
	privateCacheProvider := privatecache.NewPrivateCacheProvider(testdb, nil, testCache, false)
	psr, _ := NewMultiplePrivateStateRepository(testdb, testCache, common.Hash{}, privateCacheProvider)
	header1 := &types.Header{Number: big.NewInt(int64(1)), Root: common.Hash{123}}
	block1 := types.NewBlockWithHeader(header1)

	header2 := &types.Header{Number: big.NewInt(int64(2)), Root: common.Hash{124}}
	block2 := types.NewBlockWithHeader(header2)

	testState1, _ := psr.StatePSI(testPS1)
	emptyState, _ := psr.StatePSI(types.EmptyPrivateStateIdentifier)

	//make updates to states
	for i := byte(0); i < 10; i++ {
		addr := common.BytesToAddress([]byte{i})
		testState1.AddBalance(addr, big.NewInt(int64(i)))
		emptyState.AddBalance(addr, big.NewInt(int64(i)))
	}

	assert.Equal(t, rawdb.GetPrivateStatesTrieRoot(testdb, block1.Root()), common.Hash{})

	psr.CommitAndWrite(false, block1)

	psr, _ = NewMultiplePrivateStateRepository(testdb, testCache, rawdb.GetPrivateStatesTrieRoot(testdb, block1.Root()), privateCacheProvider)

	testState1, _ = psr.StatePSI(testPS1)
	_, _ = psr.StatePSI(types.EmptyPrivateStateIdentifier)

	removedAddress := common.BytesToAddress([]byte{1})
	testState1.Suicide(removedAddress)

	psr.CommitAndWrite(false, block2)

	psr, _ = NewMultiplePrivateStateRepository(testdb, testCache, rawdb.GetPrivateStatesTrieRoot(testdb, block2.Root()), privateCacheProvider)
	testState1, _ = psr.StatePSI(testPS1)
	emptyState, _ = psr.StatePSI(types.EmptyPrivateStateIdentifier)

	assert.False(t, testState1.Exist(removedAddress))
	assert.True(t, emptyState.Exist(removedAddress))
}
