// Copyright 2014 The go-ethereum Authors
// (original work)
// Copyright 2024 The Erigon Authors
// (modifications)
// 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 eth

import (
	"bytes"
	"testing"

	"github.com/holiman/uint256"

	"github.com/erigontech/erigon/common"
	"github.com/erigontech/erigon/execution/rlp"
	"github.com/erigontech/erigon/execution/types"
	"github.com/erigontech/erigon/node/direct"
	"github.com/erigontech/erigon/node/gointerfaces/sentryproto"
)

// Tests that the custom union field encoder and decoder works correctly.
func TestGetBlockHeadersDataEncodeDecode(t *testing.T) {
	// Create a "random" hash for testing
	var hash common.Hash
	for i := range hash {
		hash[i] = byte(i)
	}
	// Assemble some table driven tests
	tests := []struct {
		packet *GetBlockHeadersPacket
		fail   bool
	}{
		// Providing the origin as either a hash or a number should both work
		{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}}},
		{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}}},

		// Providing arbitrary query field should also work
		{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}},
		{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}},

		// Providing both the origin hash and origin number must fail
		{fail: true, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash, Number: 314}}},
	}
	// Iterate over each of the tests and try to encode and then decode
	for i, tt := range tests {
		bytes, err := rlp.EncodeToBytes(tt.packet)
		if err != nil && !tt.fail {
			t.Fatalf("test %d: failed to encode packet: %v", i, err)
		} else if err == nil && tt.fail {
			t.Fatalf("test %d: encode should have failed", i)
		}
		if !tt.fail {
			packet := new(GetBlockHeadersPacket)
			if err := rlp.DecodeBytes(bytes, packet); err != nil {
				t.Fatalf("test %d: failed to decode packet: %v", i, err)
			}
			if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount ||
				packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse {
				t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet)
			}
		}
	}
}

// TestEth66EmptyMessages tests encoding of empty eth66 messages
func TestEth66EmptyMessages(t *testing.T) {
	// All empty messages encodes to the same format
	want := common.FromHex("c4820457c0")

	for i, msg := range []any{
		// Headers
		GetBlockHeadersPacket66{1111, nil},
		BlockHeadersPacket66{1111, nil},
		// Bodies
		GetBlockBodiesPacket66{1111, nil},
		BlockBodiesPacket66{1111, nil},
		BlockBodiesRLPPacket66{1111, nil},
		// Receipts
		GetReceiptsPacket66{1111, nil},
		ReceiptsPacket66{1111, nil},
		// Block Access Lists (eth/71, EIP-8159)
		GetBlockAccessListsPacket66{1111, nil},
		BlockAccessListsPacket66{1111, nil},

		// Headers
		BlockHeadersPacket66{1111, BlockHeadersPacket([]*types.Header{})},
		// Bodies
		GetBlockBodiesPacket66{1111, GetBlockBodiesPacket([]common.Hash{})},
		BlockBodiesPacket66{1111, BlockBodiesPacket([]*types.Body{})},
		BlockBodiesRLPPacket66{1111, BlockBodiesRLPPacket([]rlp.RawValue{})},
		// Receipts
		GetReceiptsPacket66{1111, GetReceiptsPacket([]common.Hash{})},
		ReceiptsPacket66{1111, ReceiptsPacket([][]*types.Receipt{})},
		// Block Access Lists (eth/71, EIP-8159)
		GetBlockAccessListsPacket66{1111, GetBlockAccessListsPacket([]common.Hash{})},
		BlockAccessListsPacket66{1111, BlockAccessListsPacket([]rlp.RawValue{})},
	} {
		if have, _ := rlp.EncodeToBytes(msg); !bytes.Equal(have, want) {
			t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, msg, have, want)
		}
	}

}

// TestEth66Messages tests the encoding of all redefined eth66 messages
func TestEth66Messages(t *testing.T) {

	// Some basic structs used during testing
	var (
		header       *types.Header
		blockBody    *types.Body
		blockBodyRlp rlp.RawValue
		txs          []types.Transaction
		hashes       []common.Hash
		receipts     []*types.Receipt
		receiptsRlp  rlp.RawValue

		err error
	)
	header = &types.Header{
		Difficulty: *uint256.NewInt(2222),
		Number:     *uint256.NewInt(3333),
		GasLimit:   4444,
		GasUsed:    5555,
		Time:       6666,
		Extra:      []byte{0x77, 0x88},
	}
	// Init the transactions, taken from a different test
	{
		for _, hexrlp := range []string{
			"f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
			"f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
		} {
			var txn types.Transaction
			rlpdata := common.FromHex(hexrlp)
			txn, err1 := types.DecodeTransaction(rlpdata)
			if err1 != nil {
				t.Fatal(err1)
			}
			txs = append(txs, txn)
		}
	}
	// init the block body data, both object and rlp form
	blockBody = &types.Body{
		Transactions: txs,
		Uncles:       []*types.Header{header},
	}
	blockBodyRlp, err = rlp.EncodeToBytes(blockBody)
	if err != nil {
		t.Fatal(err)
	}

	hashes = []common.Hash{
		common.HexToHash("deadc0de"),
		common.HexToHash("feedbeef"),
	}
	// init the receipts
	{
		receipts = []*types.Receipt{
			{
				Status:            types.ReceiptStatusFailed,
				CumulativeGasUsed: 1,
				Logs: []*types.Log{
					{
						Address: common.BytesToAddress([]byte{0x11}),
						Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
						Data:    []byte{0x01, 0x00, 0xff},
					},
				},
				TxHash:          hashes[0],
				ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
				GasUsed:         111111,
			},
		}
		rlpData, err := rlp.EncodeToBytes(receipts)
		if err != nil {
			t.Fatal(err)
		}
		receiptsRlp = rlpData
	}

	for i, tc := range []struct {
		message any
		want    []byte
	}{
		{
			GetBlockHeadersPacket66{1111, &GetBlockHeadersPacket{HashOrNumber{hashes[0], 0}, 5, 5, false}},
			common.FromHex("e8820457e4a000000000000000000000000000000000000000000000000000000000deadc0de050580"),
		},
		{
			GetBlockHeadersPacket66{1111, &GetBlockHeadersPacket{HashOrNumber{common.Hash{}, 9999}, 5, 5, false}},
			common.FromHex("ca820457c682270f050580"),
		},
		{
			BlockHeadersPacket66{1111, BlockHeadersPacket{header}},
			common.FromHex("f90202820457f901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"),
		},
		{
			GetBlockBodiesPacket66{1111, GetBlockBodiesPacket(hashes)},
			common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"),
		},
		{
			BlockBodiesPacket66{1111, BlockBodiesPacket([]*types.Body{blockBody})},
			common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"),
		},
		{ // Identical to non-rlp-shortcut version
			BlockBodiesRLPPacket66{1111, BlockBodiesRLPPacket([]rlp.RawValue{blockBodyRlp})},
			common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"),
		},
		{
			GetReceiptsPacket66{1111, GetReceiptsPacket(hashes)},
			common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"),
		},
		{
			ReceiptsPacket66{1111, ReceiptsPacket([][]*types.Receipt{receipts})},
			common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"),
		},
		{
			ReceiptsRLPPacket66{1111, ReceiptsRLPPacket([]rlp.RawValue{receiptsRlp})},
			common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"),
		},
	} {
		if have, _ := rlp.EncodeToBytes(tc.message); !bytes.Equal(have, tc.want) {
			t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, tc.message, have, tc.want)
		}
	}
}

// TestBlockAccessListsPacket66RoundTrip verifies the eth/71 (EIP-8159) BAL
// exchange packet types encode and decode losslessly across the entry shapes
// covered here: nil, empty list, the "genuinely empty BAL" sentinel (0xc0,
// per post-#11553 spec), populated BAL bytes, and a multi-entry mix. The
// "not available" sentinel (0x80) is not exercised by this test.
func TestBlockAccessListsPacket66RoundTrip(t *testing.T) {
	const reqID uint64 = 0x12345678

	hashA := common.Hash{0xaa, 0xbb, 0xcc}
	hashB := common.Hash{0x11, 0x22, 0x33}
	hashC := common.Hash{0x42}

	// Non-trivial BAL payload stand-in: a valid RLP list of two byte-strings.
	// We don't construct a full types.BlockAccessList here — wire layer treats
	// the payload as opaque RLP bytes; content validation happens at the
	// callsite via keccak256 comparison against header.BlockAccessListHash.
	bal, err := rlp.EncodeToBytes([]any{[]byte{0x01, 0x02}, []byte{0x03}})
	if err != nil {
		t.Fatalf("encode stub BAL: %v", err)
	}
	emptyRLPList := common.FromHex("c0") // EIP-8159 (post-#11553): 0xc0 = "genuinely empty BAL" (0x80 = "not available")

	t.Run("GetBlockAccessLists", func(t *testing.T) {
		cases := [][]common.Hash{
			nil,
			{},
			{hashA},
			{hashA, hashB, hashC},
		}
		for i, hashes := range cases {
			msg := GetBlockAccessListsPacket66{RequestId: reqID, GetBlockAccessListsPacket: hashes}
			enc, err := rlp.EncodeToBytes(&msg)
			if err != nil {
				t.Fatalf("case %d: encode: %v", i, err)
			}
			var dec GetBlockAccessListsPacket66
			if err := rlp.DecodeBytes(enc, &dec); err != nil {
				t.Fatalf("case %d: decode: %v", i, err)
			}
			if dec.RequestId != reqID {
				t.Fatalf("case %d: request id: have %d want %d", i, dec.RequestId, reqID)
			}
			if len(dec.GetBlockAccessListsPacket) != len(hashes) {
				t.Fatalf("case %d: hash count: have %d want %d", i, len(dec.GetBlockAccessListsPacket), len(hashes))
			}
			for j := range hashes {
				if dec.GetBlockAccessListsPacket[j] != hashes[j] {
					t.Fatalf("case %d idx %d: hash mismatch: have %x want %x", i, j, dec.GetBlockAccessListsPacket[j], hashes[j])
				}
			}
		}
	})

	t.Run("BlockAccessLists", func(t *testing.T) {
		cases := [][]rlp.RawValue{
			nil,
			{},
			{emptyRLPList},           // single "genuinely empty BAL" entry
			{bal},                    // single populated BAL
			{bal, emptyRLPList, bal}, // mixed — populated and genuinely-empty BALs
		}
		for i, bals := range cases {
			msg := BlockAccessListsPacket66{RequestId: reqID, BlockAccessListsPacket: bals}
			enc, err := rlp.EncodeToBytes(&msg)
			if err != nil {
				t.Fatalf("case %d: encode: %v", i, err)
			}
			var dec BlockAccessListsPacket66
			if err := rlp.DecodeBytes(enc, &dec); err != nil {
				t.Fatalf("case %d: decode: %v", i, err)
			}
			if dec.RequestId != reqID {
				t.Fatalf("case %d: request id: have %d want %d", i, dec.RequestId, reqID)
			}
			if len(dec.BlockAccessListsPacket) != len(bals) {
				t.Fatalf("case %d: bal count: have %d want %d", i, len(dec.BlockAccessListsPacket), len(bals))
			}
			for j := range bals {
				if !bytes.Equal(dec.BlockAccessListsPacket[j], bals[j]) {
					t.Fatalf("case %d idx %d: payload mismatch: have %x want %x", i, j, dec.BlockAccessListsPacket[j], bals[j])
				}
			}
		}
	})
}

// TestEth71ProtocolRegistration verifies the eth/71 version is registered in
// the protocol name/length tables and that the two new message codes route to
// their sentry MessageId counterparts via ToProto/FromProto.
func TestEth71ProtocolRegistration(t *testing.T) {
	if name, ok := ProtocolToString[direct.ETH71]; !ok || name != "eth71" {
		t.Fatalf("ProtocolToString[ETH71] = (%q, %v), want (eth71, true)", name, ok)
	}
	if length, ok := ProtocolLengths[direct.ETH71]; !ok || length != 20 {
		t.Fatalf("ProtocolLengths[ETH71] = (%d, %v), want (20, true)", length, ok)
	}

	fwd := ToProto[direct.ETH71]
	if fwd == nil {
		t.Fatal("ToProto has no ETH71 entry")
	}
	if got := fwd[GetBlockAccessListsMsg]; got != sentryproto.MessageId_GET_BLOCK_ACCESS_LISTS_71 {
		t.Errorf("ToProto[ETH71][GetBlockAccessListsMsg] = %v, want GET_BLOCK_ACCESS_LISTS_71", got)
	}
	if got := fwd[BlockAccessListsMsg]; got != sentryproto.MessageId_BLOCK_ACCESS_LISTS_71 {
		t.Errorf("ToProto[ETH71][BlockAccessListsMsg] = %v, want BLOCK_ACCESS_LISTS_71", got)
	}

	rev := FromProto[direct.ETH71]
	if rev == nil {
		t.Fatal("FromProto has no ETH71 entry")
	}
	if got := rev[sentryproto.MessageId_GET_BLOCK_ACCESS_LISTS_71]; got != GetBlockAccessListsMsg {
		t.Errorf("FromProto[ETH71][GET_BLOCK_ACCESS_LISTS_71] = %v, want GetBlockAccessListsMsg", got)
	}
	if got := rev[sentryproto.MessageId_BLOCK_ACCESS_LISTS_71]; got != BlockAccessListsMsg {
		t.Errorf("FromProto[ETH71][BLOCK_ACCESS_LISTS_71] = %v, want BlockAccessListsMsg", got)
	}
}
