// Copyright 2024 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package trie

import (
	"bytes"
	"fmt"
	"reflect"
	"testing"

	"github.com/erigontech/erigon/common"
	"github.com/erigontech/erigon/common/crypto"
	"github.com/erigontech/erigon/common/length"
	"github.com/erigontech/erigon/common/u256"
	"github.com/erigontech/erigon/execution/types/accounts"
)

func TestTrieDeleteSubtree_ShortNode(t *testing.T) {
	trie := newEmpty()
	key := []byte{uint8(1)}
	val := []byte("1")

	trie.Update(key, val)
	v, ok := trie.Get(key)
	if ok == false || bytes.Equal(v, val) == false {
		t.Fatal("incorrect")
	}
	//remove unknown
	trie.DeleteSubtree([]byte{uint8(2)})
	v, ok = trie.Get(key)
	if ok == false || bytes.Equal(v, val) == false {
		t.Fatal("incorrect")
	}

	//remove by key
	trie.DeleteSubtree(key)

	v, _ = trie.Get(key)
	if v != nil {
		t.Fatal("must be false")
	}
}

func TestTrieDeleteSubtree_ShortNode_Debug(t *testing.T) {
	trie := newEmpty()
	addr1 := common.HexToAddress("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")
	addr2 := common.HexToAddress("0xfc597da4849c0d854629216d9e297bbca7bb4616")

	key := []byte{uint8(1)}
	val := []byte{uint8(1)}

	keyHash, err := common.HashData(key)
	if err != nil {
		t.Fatal(err)
	}

	addrHash1, err := common.HashData(addr1[:])
	if err != nil {
		t.Fatal(err)
	}

	addrHash2, err := common.HashData(addr2[:])
	if err != nil {
		t.Fatal(err)
	}

	key1 := GenerateCompositeTrieKey(addrHash1, keyHash)
	key2 := GenerateCompositeTrieKey(addrHash2, keyHash)
	trie.Update(key1, val)
	trie.Update(key2, val)

	trie.DeleteSubtree(addrHash1[:])

	v, ok := trie.Get(key2)
	if ok == false || bytes.Equal(v, val) == false {
		t.Fatal("incorrect")
	}

	v, _ = trie.Get(key1)
	if v != nil {
		t.Fatal("must be nil")
	}
}

func GenerateCompositeTrieKey(addressHash common.Hash, seckey common.Hash) []byte {
	compositeKey := make([]byte, 0, length.Hash+length.Hash)
	compositeKey = append(compositeKey, addressHash[:]...)
	compositeKey = append(compositeKey, seckey[:]...)
	return compositeKey
}

func TestTrieDeleteSubtree_ShortNode_LongPrefix(t *testing.T) {
	trie := newEmpty()
	key := []byte{uint8(1), uint8(1)}
	prefix := []byte{uint8(1)}
	val := []byte("1")

	trie.Update(key, val)
	v, ok := trie.Get(key)
	if ok == false || bytes.Equal(v, val) == false {
		t.Fatal("incorrect")
	}
	//remove unknown
	trie.Delete([]byte{uint8(2)})
	v, ok = trie.Get(key)
	if ok == false || bytes.Equal(v, val) == false {
		t.Fatal("incorrect")
	}

	//remove by key
	trie.DeleteSubtree(prefix)
	v, _ = trie.Get(key)
	t.Log(v)
	if v != nil {
		t.Fatal("must be false")
	}
}

func TestTrieDeleteSubtree_DuoNode(t *testing.T) {
	trie := newEmpty()
	key := []byte{uint8(1)}
	val := []byte{uint8(1)}
	keyExist := []byte{uint8(2)}
	valExist := []byte{uint8(2)}

	trie.Update(key, val)
	trie.Update(keyExist, valExist)
	v, ok := trie.Get(key)
	if ok == false || bytes.Equal(v, val) == false {
		t.Fatal("incorrect")
	}

	trie.DeleteSubtree(key)

	v, _ = trie.Get(key)
	if v != nil {
		t.Fatal("must be nil")
	}
	v, _ = trie.Get(keyExist)
	if bytes.Equal(v, valExist) == false {
		t.Fatal("must be false")
	}
}

func TestTrieDeleteSubtree_TwoDuoNode_FullMatch(t *testing.T) {
	trie := newEmpty()
	key1 := []byte{uint8(1), uint8(1), uint8(1)}
	key2 := []byte{uint8(1), uint8(1), uint8(2)}
	key3 := []byte{uint8(1), uint8(2), uint8(3)}

	val1 := []byte{uint8(1)}
	val2 := []byte{uint8(2)}
	val3 := []byte{uint8(3)}

	trie.Update(key1, val1)
	trie.Update(key2, val2)
	trie.Update(key3, val3)

	trie.DeleteSubtree(key1)
	trie.DeleteSubtree(key2)

	v, _ := trie.Get(key1)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key2)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key3)
	if bytes.Equal(v, val3) != true {
		t.Fatal("must be equals", v)
	}
}

func TestTrieDeleteSubtree_DuoNode_PartialMatch(t *testing.T) {
	trie := newEmpty()

	key1 := []byte{uint8(1), uint8(1), uint8(1)}
	key2 := []byte{uint8(1), uint8(1), uint8(2)}
	key3 := []byte{uint8(1), uint8(2), uint8(3)}
	partialKey := []byte{uint8(1), uint8(1)}

	val1 := []byte{uint8(1)}
	val2 := []byte{uint8(2)}
	val3 := []byte{uint8(3)}

	trie.Update(key1, val1)
	trie.Update(key2, val2)
	trie.Update(key3, val3)

	trie.DeleteSubtree(partialKey)
	v, _ := trie.Get(key1)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key2)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key3)
	if bytes.Equal(v, val3) != true {
		t.Fatal("must be equals", v)
	}
}

func TestTrieDeleteSubtree_FromFullNode_PartialMatch(t *testing.T) {
	trie := newEmpty()
	key1 := []byte{uint8(1), uint8(1), uint8(1)}
	key2 := []byte{uint8(1), uint8(1), uint8(2)}
	key3 := []byte{uint8(1), uint8(1), uint8(3)}
	key4 := []byte{uint8(1), uint8(2), uint8(4)}
	partialKey := []byte{uint8(1), uint8(1)}

	val1 := []byte{uint8(1)}
	val2 := []byte{uint8(2)}
	val3 := []byte{uint8(3)}
	val4 := []byte{uint8(4)}

	trie.Update(key1, val1)
	trie.Update(key2, val2)
	trie.Update(key3, val3)
	trie.Update(key4, val4)

	trie.DeleteSubtree(partialKey)

	v, _ := trie.Get(key1)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key2)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key3)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key4)
	if bytes.Equal(v, val4) != true {
		t.Fatal("must be equals", v)
	}
}

func TestTrieDeleteSubtree_RemoveFullNode_PartialMatch(t *testing.T) {
	trie := newEmpty()
	key1 := []byte{uint8(1), uint8(1), uint8(1), uint8(1)}
	key2 := []byte{uint8(1), uint8(1), uint8(2), uint8(1)}
	key3 := []byte{uint8(1), uint8(1), uint8(3), uint8(1)}
	key4 := []byte{uint8(1), uint8(2), uint8(4)}
	partialKey := []byte{uint8(1), uint8(1)}

	val1 := []byte{uint8(1)}
	val2 := []byte{uint8(2)}
	val3 := []byte{uint8(3)}
	val4 := []byte{uint8(4)}

	trie.Update(key1, val1)
	trie.Update(key2, val2)
	trie.Update(key3, val3)
	trie.Update(key4, val4)

	trie.DeleteSubtree(partialKey)

	v, _ := trie.Get(key1)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key2)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key3)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key4)
	if bytes.Equal(v, val4) != true {
		t.Fatal("must be equals", v)
	}
}

func TestTrieDeleteSubtree_FullNode_FullMatch(t *testing.T) {
	trie := newEmpty()
	key1 := []byte{uint8(1), uint8(1), uint8(1)}
	key2 := []byte{uint8(1), uint8(1), uint8(2)}
	key3 := []byte{uint8(1), uint8(1), uint8(3)}
	key4 := []byte{uint8(1), uint8(2), uint8(4)}

	val1 := []byte{uint8(1)}
	val2 := []byte{uint8(2)}
	val3 := []byte{uint8(3)}
	val4 := []byte{uint8(4)}

	trie.Update(key1, val1)
	trie.Update(key2, val2)
	trie.Update(key3, val3)
	trie.Update(key4, val4)

	trie.DeleteSubtree(key1)
	trie.DeleteSubtree(key2)

	v, _ := trie.Get(key1)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key2)
	if v != nil {
		t.Fatal("must be nil", v)
	}

	v, _ = trie.Get(key3)
	if bytes.Equal(v, val3) != true {
		t.Fatal("must be equals", v)
	}
}

func TestTrieDeleteSubtree_ValueNode_PartialMatch(t *testing.T) {
	trie := newEmpty()
	key := []byte{uint8(1)}
	val := []byte{uint8(1)}
	keyExist := []byte{uint8(2)}
	valExist := []byte{uint8(2)}
	trie.Update(key, val)
	trie.Update(keyExist, valExist)
	v, ok := trie.Get(key)
	if ok == false || bytes.Equal(v, val) == false {
		t.Fatal("incorrect")
	}
	trie.DeleteSubtree(key)
	v, _ = trie.Get(key)
	if v != nil {
		t.Fatal("must be empty")
	}
	v, ok = trie.Get(keyExist)
	if ok == false || bytes.Equal(v, valExist) == false {
		t.Fatal("must be true")
	}
}

func TestAccountNotRemovedAfterRemovingSubtrieAfterAccount(t *testing.T) {
	acc := &accounts.Account{
		Nonce:       2,
		Incarnation: 2,
		Balance:     u256.U64(200),
		Root:        EmptyRoot,
		CodeHash:    accounts.InternCodeHash(emptyState),
	}

	trie := newEmpty()
	key, err := crypto.GenerateKey()
	if err != nil {
		t.Fatal(err)
	}
	pubAddr := crypto.PubkeyToAddress(key.PublicKey)
	addrHash, err := common.HashData(pubAddr[:])
	if err != nil {
		t.Fatal(err)
	}
	trie.UpdateAccount(addrHash[:], acc)

	accRes1, _ := trie.GetAccount(addrHash[:])
	if reflect.DeepEqual(acc, accRes1) == false {
		t.Fatal("not equal", addrHash)
	}

	val1 := []byte("1")
	dataKey1, err := common.HashData([]byte("1"))
	if err != nil {
		t.Fatal(err)
	}

	val2 := []byte("2")
	dataKey2, err := common.HashData([]byte("2"))
	if err != nil {
		t.Fatal(err)
	}

	fmt.Println("=============================")
	ck1 := GenerateCompositeTrieKey(addrHash, dataKey1)
	ck2 := GenerateCompositeTrieKey(addrHash, dataKey2)
	trie.Update(ck1, val1)
	trie.Update(ck2, val2)

	trie.DeleteSubtree(addrHash[:])

	accRes2, _ := trie.GetAccount(addrHash[:])
	if reflect.DeepEqual(acc, accRes2) == false {
		t.Fatal("account was deleted", addrHash)
	}

}
