package types

import (
	"bytes"
	"errors"
	"reflect"
	"strings"
	"testing"

	"github.com/holiman/uint256"

	"github.com/erigontech/erigon/common"
	"github.com/erigontech/erigon/execution/rlp"
	"github.com/erigontech/erigon/execution/types/accounts"
)

func TestBlockAccessListValidateOrdering(t *testing.T) {
	var addrA, addrB common.Address
	addrA[19] = 0x02
	addrB[19] = 0x01

	list := BlockAccessList{
		{Address: accounts.InternAddress(addrA)},
		{Address: accounts.InternAddress(addrB)},
	}
	if err := list.Validate(); err == nil {
		t.Fatalf("expected ordering error, got nil")
	}
}

func TestAccountChangesEncodeRejectsUnsortedReads(t *testing.T) {
	var addr common.Address
	addr[19] = 0x01
	var slotA, slotB common.Hash
	slotA[31] = 0x02
	slotB[31] = 0x01

	ac := &AccountChanges{
		Address:      accounts.InternAddress(addr),
		StorageReads: []accounts.StorageKey{accounts.InternKey(slotA), accounts.InternKey(slotB)},
	}

	var buf bytes.Buffer
	if err := ac.EncodeRLP(&buf); err == nil || !strings.Contains(err.Error(), "reads must be strictly increasing") {
		t.Fatalf("expected storage read ordering error, got %v", err)
	}
}

func TestDecodeBalanceChangesRejectsOutOfOrderIndices(t *testing.T) {
	payload, err := rlp.EncodeToBytes([][]any{
		{uint64(2), []byte{0x01}},
		{uint64(1), []byte{0x01}},
	})
	if err != nil {
		t.Fatalf("failed to build payload: %v", err)
	}

	stream := rlp.NewStream(bytes.NewReader(payload), uint64(len(payload)))
	if _, err := decodeBalanceChanges(stream); err == nil || !strings.Contains(err.Error(), "indices") {
		t.Fatalf("expected index ordering error, got %v", err)
	}
}

func TestBlockAccessListRLPEncoding(t *testing.T) {
	bal := BlockAccessList{
		{
			Address: accounts.InternAddress(common.HexToAddress("0x00000000000000000000000000000000000000aa")),
			StorageChanges: []*SlotChanges{
				{
					Slot: accounts.InternKey(common.HexToHash("0x01")),
					Changes: []*StorageChange{
						{Index: 1, Value: *uint256.NewInt(2)},
						{Index: 5, Value: *uint256.NewInt(3)},
					},
				},
			},
			StorageReads: []accounts.StorageKey{
				accounts.InternKey(common.HexToHash("0x02")),
			},
			BalanceChanges: []*BalanceChange{
				{Index: 1, Value: *uint256.NewInt(4)},
			},
			NonceChanges: []*NonceChange{
				{Index: 9, Value: 7},
			},
			CodeChanges: []*CodeChange{
				{Index: 2, Bytecode: []byte{0xbe, 0xef}},
			},
		},
	}

	encoded, err := rlp.EncodeToBytes(bal)
	if err != nil {
		t.Fatalf("encode failed: %v", err)
	}

	// Fixed-size encoding: slot keys and storage values are 32-byte strings,
	// storage reads are 32-byte strings, balances are 16-byte strings.
	expected := common.FromHex("0xf0ef9400000000000000000000000000000000000000aac9c801c6c20102c20503c102c3c20104c3c20907c5c40282beef")
	if !bytes.Equal(encoded, expected) {
		t.Fatalf("unexpected encoding\nhave: %x\nwant: %x", encoded, expected)
	}

	var decoded BlockAccessList
	if err := rlp.DecodeBytes(encoded, &decoded); err != nil {
		t.Fatalf("decode failed: %v", err)
	}

	if !reflect.DeepEqual(decoded, bal) {
		t.Fatalf("decoded BAL mismatch\nhave: %#v\nwant: %#v", decoded, bal)
	}
}

func TestBlockAccessListValidateMaxItems(t *testing.T) {
	makeBAL := func(numAccounts, slotsPerAccount int) BlockAccessList {
		bal := make(BlockAccessList, numAccounts)
		for i := range bal {
			var addr common.Address
			addr[18] = byte(i >> 8)
			addr[19] = byte(i)
			reads := make([]accounts.StorageKey, slotsPerAccount)
			for j := range reads {
				var h common.Hash
				h[30] = byte(j >> 8)
				h[31] = byte(j)
				reads[j] = accounts.InternKey(h)
			}
			bal[i] = &AccountChanges{
				Address:      accounts.InternAddress(addr),
				StorageReads: reads,
			}
		}
		return bal
	}

	// 10 accounts + 5 slots each = 60 items; gasLimit 120000 → max 60 items → exactly at limit
	bal := makeBAL(10, 5)
	if err := bal.ValidateMaxItems(120_000); err != nil {
		t.Fatalf("expected valid at limit, got: %v", err)
	}

	// Same BAL with lower gas limit → over limit
	if err := bal.ValidateMaxItems(119_999); err == nil {
		t.Fatal("expected error for over-limit BAL")
	}

	// Empty BAL always valid
	if err := (BlockAccessList{}).ValidateMaxItems(0); err != nil {
		t.Fatalf("expected empty BAL valid, got: %v", err)
	}
}

func TestBlockAccessListSlotUniqueness(t *testing.T) {
	var addr common.Address
	addr[19] = 0x01
	slot := common.HexToHash("0x01")

	ac := &AccountChanges{
		Address: accounts.InternAddress(addr),
		StorageChanges: []*SlotChanges{
			{
				Slot:    accounts.InternKey(slot),
				Changes: []*StorageChange{{Index: 0, Value: *uint256.NewInt(1)}},
			},
		},
		StorageReads: []accounts.StorageKey{accounts.InternKey(slot)},
	}
	bal := BlockAccessList{ac}
	if err := bal.Validate(); err == nil {
		t.Fatal("expected error for slot in both changes and reads")
	}
}

func TestBlockAccessListHashEmpty(t *testing.T) {
	var bal BlockAccessList
	if h := bal.Hash(); h != common.HexToHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") {
		t.Fatalf("unexpected empty BAL hash: %s", h)
	}

	if err := bal.Validate(); err != nil {
		t.Fatalf("empty BAL should be valid: %v", err)
	}
}

// TestBlockAccessListEmptyRoundTrip verifies that an empty BAL encodes to the
// canonical empty RLP list (0xc0) and decodes back to a non-nil empty slice.
// EIP-7928 requires: "When no state changes are present, this field is the
// empty RLP list 0xc0, i.e. rlp.encode([])."
func TestBlockAccessListEmptyRoundTrip(t *testing.T) {
	// Encode nil BAL — must produce 0xc0.
	encoded, err := EncodeBlockAccessListBytes(nil)
	if err != nil {
		t.Fatalf("encode nil BAL: %v", err)
	}
	if !bytes.Equal(encoded, []byte{0xc0}) {
		t.Fatalf("nil BAL encoding: got %x, want c0", encoded)
	}

	// Encode empty (non-nil) BAL — must also produce 0xc0.
	encoded2, err := EncodeBlockAccessListBytes(make(BlockAccessList, 0))
	if err != nil {
		t.Fatalf("encode empty BAL: %v", err)
	}
	if !bytes.Equal(encoded2, []byte{0xc0}) {
		t.Fatalf("empty BAL encoding: got %x, want c0", encoded2)
	}

	// Decode 0xc0 — must produce non-nil empty slice (not nil).
	decoded, err := DecodeBlockAccessListBytes(encoded)
	if err != nil {
		t.Fatalf("decode empty BAL: %v", err)
	}
	if decoded == nil {
		t.Fatal("decoded empty BAL must be non-nil")
	}
	if len(decoded) != 0 {
		t.Fatalf("decoded empty BAL length: got %d, want 0", len(decoded))
	}
}

// TestBlockAccessListRejectsEmptySlotChanges verifies that a BlockAccessList
// containing a storage slot with zero actual changes is strictly rejected.
// As per EIP-7928: "Each SlotChanges entry MUST contain at least one StorageChange."
func TestBlockAccessListRejectsEmptySlotChanges(t *testing.T) {
	var addr common.Address
	addr[19] = 0x01
	slot := common.HexToHash("0x01")

	ac := &AccountChanges{
		Address: accounts.InternAddress(addr),
		StorageChanges: []*SlotChanges{
			{
				Slot:    accounts.InternKey(slot),
				Changes: []*StorageChange{}, // Intentionally empty list
			},
		},
	}

	bal := BlockAccessList{ac}
	err := bal.Validate()

	if err == nil {
		t.Fatal("expected error for empty slot changes, but got nil")
	}
	if !strings.Contains(err.Error(), "empty slot changes") {
		t.Fatalf("expected 'empty slot changes' error, but got: %v", err)
	}
}

// balTestAccountRLP hand-encodes a minimal AccountChanges entry: a 20-byte
// address whose last byte is lastAddrByte, followed by five empty change/read
// lists (storage changes, storage reads, balance, nonce, code changes).
func balTestAccountRLP(lastAddrByte byte) []byte {
	acc := []byte{0xda, 0x94}
	addr := make([]byte, 20)
	addr[19] = lastAddrByte
	acc = append(acc, addr...)
	return append(acc, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0)
}

func balTestWrapList(items ...[]byte) []byte {
	var content []byte
	for _, item := range items {
		content = append(content, item...)
	}
	if len(content) >= 56 {
		panic("balTestWrapList supports short lists only")
	}
	return append([]byte{0xc0 + byte(len(content))}, content...)
}

func TestDecodeBlockAccessListBytesMalformedVsInvalid(t *testing.T) {
	valid, err := DecodeBlockAccessListBytes(balTestWrapList(balTestAccountRLP(1), balTestAccountRLP(2)))
	if err != nil {
		t.Fatalf("valid two-account list: %v", err)
	}
	if len(valid) != 2 {
		t.Fatalf("valid two-account list: got %d accounts, want 2", len(valid))
	}
	emptyBal, err := DecodeBlockAccessListBytes([]byte{0xc0})
	if err != nil || emptyBal == nil || len(emptyBal) != 0 {
		t.Fatalf("empty list: bal=%v err=%v", emptyBal, err)
	}
	malformed := map[string][]byte{
		"empty input":     {},
		"string not list": {0x80},
		"truncated list":  {0xc1},
		"trailing byte":   {0xc0, 0x00},
		"trailing list":   {0xc0, 0xc0},
	}
	for name, data := range malformed {
		_, err := DecodeBlockAccessListBytes(data)
		if err == nil {
			t.Fatalf("%s: expected error", name)
		}
		if errors.Is(err, ErrInvalidBlockAccessList) {
			t.Fatalf("%s: malformed RLP must not map to ErrInvalidBlockAccessList: %v", name, err)
		}
	}
	emptySlotChanges := append([]byte{0xdd, 0x94}, make([]byte, 20)...)
	emptySlotChanges = append(emptySlotChanges, 0xc3, 0xc2, 0x01, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0)
	semanticallyInvalid := map[string][]byte{
		"duplicate address":  balTestWrapList(balTestAccountRLP(1), balTestAccountRLP(1)),
		"descending address": balTestWrapList(balTestAccountRLP(2), balTestAccountRLP(1)),
		"empty slot changes": balTestWrapList(emptySlotChanges),
	}
	for name, data := range semanticallyInvalid {
		_, err := DecodeBlockAccessListBytes(data)
		if err == nil {
			t.Fatalf("%s: expected error", name)
		}
		if !errors.Is(err, ErrInvalidBlockAccessList) {
			t.Fatalf("%s: expected ErrInvalidBlockAccessList, got: %v", name, err)
		}
	}
}
