package common

import (
	"encoding/binary"
	"math/big"
	"testing"
	"time"

	eth_common "github.com/ethereum/go-ethereum/common"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/wormhole-foundation/wormhole/sdk/vaa"
)

func encodePayloadBytes(payload *vaa.TransferPayloadHdr) []byte {
	bytes := make([]byte, 101)
	bytes[0] = payload.Type

	amtBytes := payload.Amount.Bytes()
	if len(amtBytes) > 32 {
		panic("amount will not fit in 32 bytes!")
	}
	copy(bytes[33-len(amtBytes):33], amtBytes)

	copy(bytes[33:65], payload.OriginAddress.Bytes())
	binary.BigEndian.PutUint16(bytes[65:67], uint16(payload.OriginChain))
	copy(bytes[67:99], payload.TargetAddress.Bytes())
	binary.BigEndian.PutUint16(bytes[99:101], uint16(payload.TargetChain))
	return bytes
}

func TestSerializeAndDeserializeOfMessagePublication(t *testing.T) {
	originAddress, err := vaa.StringToAddress("0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E") //nolint:gosec
	require.NoError(t, err)

	targetAddress, err := vaa.StringToAddress("0x707f9118e33a9b8998bea41dd0d46f38bb963fc8")
	require.NoError(t, err)

	tokenBridgeAddress, err := vaa.StringToAddress("0x707f9118e33a9b8998bea41dd0d46f38bb963fc8")
	require.NoError(t, err)

	payload1 := &vaa.TransferPayloadHdr{
		Type:          0x01,
		Amount:        big.NewInt(27000000000),
		OriginAddress: originAddress,
		OriginChain:   vaa.ChainIDEthereum,
		TargetAddress: targetAddress,
		TargetChain:   vaa.ChainIDPolygon,
	}

	payloadBytes1 := encodePayloadBytes(payload1)

	msg1 := &MessagePublication{
		TxHash:           eth_common.HexToHash("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
		Timestamp:        time.Unix(int64(1654516425), 0),
		Nonce:            123456,
		Sequence:         789101112131415,
		EmitterChain:     vaa.ChainIDEthereum,
		EmitterAddress:   tokenBridgeAddress,
		Payload:          payloadBytes1,
		ConsistencyLevel: 32,
	}

	bytes, err := msg1.Marshal()
	require.NoError(t, err)

	msg2, err := UnmarshalMessagePublication(bytes)
	require.NoError(t, err)
	assert.Equal(t, msg1, msg2)

	payload2, err := vaa.DecodeTransferPayloadHdr(msg2.Payload)
	require.NoError(t, err)

	assert.Equal(t, payload1, payload2)
}

func TestSerializeAndDeserializeOfMessagePublicationWithBigPayload(t *testing.T) {
	tokenBridgeAddress, err := vaa.StringToAddress("0x707f9118e33a9b8998bea41dd0d46f38bb963fc8")
	require.NoError(t, err)

	// Create a payload of more than 1000 bytes.
	var payload1 []byte
	for i := 0; i < 2000; i++ {
		ch := i % 255
		payload1 = append(payload1, byte(ch))
	}

	msg1 := &MessagePublication{
		TxHash:           eth_common.HexToHash("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
		Timestamp:        time.Unix(int64(1654516425), 0),
		Nonce:            123456,
		Sequence:         789101112131415,
		EmitterChain:     vaa.ChainIDEthereum,
		EmitterAddress:   tokenBridgeAddress,
		Payload:          payload1,
		ConsistencyLevel: 32,
	}

	bytes, err := msg1.Marshal()
	require.NoError(t, err)

	msg2, err := UnmarshalMessagePublication(bytes)
	require.NoError(t, err)

	assert.Equal(t, msg1, msg2)
}

func TestMarshalUnmarshalJSONOfMessagePublication(t *testing.T) {
	originAddress, err := vaa.StringToAddress("0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E") //nolint:gosec
	require.NoError(t, err)

	targetAddress, err := vaa.StringToAddress("0x707f9118e33a9b8998bea41dd0d46f38bb963fc8")
	require.NoError(t, err)

	tokenBridgeAddress, err := vaa.StringToAddress("0x707f9118e33a9b8998bea41dd0d46f38bb963fc8")
	require.NoError(t, err)

	payload1 := &vaa.TransferPayloadHdr{
		Type:          0x01,
		Amount:        big.NewInt(27000000000),
		OriginAddress: originAddress,
		OriginChain:   vaa.ChainIDEthereum,
		TargetAddress: targetAddress,
		TargetChain:   vaa.ChainIDPolygon,
	}

	payloadBytes1 := encodePayloadBytes(payload1)

	msg1 := &MessagePublication{
		TxHash:           eth_common.HexToHash("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
		Timestamp:        time.Unix(int64(1654516425), 0),
		Nonce:            123456,
		Sequence:         789101112131415,
		EmitterChain:     vaa.ChainIDEthereum,
		EmitterAddress:   tokenBridgeAddress,
		Payload:          payloadBytes1,
		ConsistencyLevel: 32,
	}

	bytes, err := msg1.MarshalJSON()
	require.NoError(t, err)

	var msg2 MessagePublication
	err = msg2.UnmarshalJSON(bytes)
	require.NoError(t, err)
	assert.Equal(t, *msg1, msg2)

	payload2, err := vaa.DecodeTransferPayloadHdr(msg2.Payload)
	require.NoError(t, err)

	assert.Equal(t, *payload1, *payload2)
}

func TestMessageIDString(t *testing.T) {
	addr, err := vaa.StringToAddress("0x0290fb167208af455bb137780163b7b7a9a10c16")
	require.NoError(t, err)

	type test struct {
		label  string
		input  MessagePublication
		output string
	}

	tests := []test{
		{label: "simple",
			input:  MessagePublication{Sequence: 1, EmitterChain: vaa.ChainIDEthereum, EmitterAddress: addr},
			output: "2/0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16/1"},
		{label: "missing sequence",
			input:  MessagePublication{EmitterChain: vaa.ChainIDEthereum, EmitterAddress: addr},
			output: "2/0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16/0"},
		{label: "missing chain id",
			input:  MessagePublication{Sequence: 1, EmitterAddress: addr},
			output: "0/0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16/1"},
		{label: "missing emitter address",
			input:  MessagePublication{Sequence: 1, EmitterChain: vaa.ChainIDEthereum},
			output: "2/0000000000000000000000000000000000000000000000000000000000000000/1"},
		{label: "empty message",
			input:  MessagePublication{},
			output: "0/0000000000000000000000000000000000000000000000000000000000000000/0"},
	}

	for _, tc := range tests {
		t.Run(tc.label, func(t *testing.T) {
			assert.Equal(t, tc.output, tc.input.MessageIDString())
		})
	}
}

func TestMessageID(t *testing.T) {
	addr, err := vaa.StringToAddress("0x0290fb167208af455bb137780163b7b7a9a10c16")
	require.NoError(t, err)

	type test struct {
		label  string
		input  MessagePublication
		output []byte
	}

	tests := []test{
		{label: "simple",
			input:  MessagePublication{Sequence: 1, EmitterChain: vaa.ChainIDEthereum, EmitterAddress: addr},
			output: []byte("2/0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16/1")},
		{label: "missing sequence",
			input:  MessagePublication{EmitterChain: vaa.ChainIDEthereum, EmitterAddress: addr},
			output: []byte("2/0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16/0")},
		{label: "missing chain id",
			input:  MessagePublication{Sequence: 1, EmitterAddress: addr},
			output: []byte("0/0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16/1")},
		{label: "missing emitter address",
			input:  MessagePublication{Sequence: 1, EmitterChain: vaa.ChainIDEthereum},
			output: []byte("2/0000000000000000000000000000000000000000000000000000000000000000/1")},
		{label: "empty message",
			input:  MessagePublication{},
			output: []byte("0/0000000000000000000000000000000000000000000000000000000000000000/0")},
	}

	for _, tc := range tests {
		t.Run(tc.label, func(t *testing.T) {
			assert.Equal(t, tc.output, tc.input.MessageID())
		})
	}
}
