package routing

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"testing"

	"github.com/btcsuite/btcd/btcec/v2"
	sphinx "github.com/lightningnetwork/lightning-onion"
	"github.com/lightningnetwork/lnd/routing/route"
	"github.com/stretchr/testify/require"
)

// TestIntermediatePayloadSize tests the payload size functions of the
// PrivateEdge and the BlindedEdge.
func TestIntermediatePayloadSize(t *testing.T) {
	t.Parallel()

	testPrivKeyBytes, _ := hex.DecodeString("e126f68f7eafcc8b74f54d269fe" +
		"206be715000f94dac067d1c04a8ca3b2db734")
	_, blindedPoint := btcec.PrivKeyFromBytes(testPrivKeyBytes)

	testCases := []struct {
		name    string
		hop     route.Hop
		nextHop uint64
		edge    AdditionalEdge
	}{
		{
			name: "Legacy payload private edge",
			hop: route.Hop{
				AmtToForward:     1000,
				OutgoingTimeLock: 600000,
				ChannelID:        3432483437438,
				LegacyPayload:    true,
			},
			nextHop: 1,
			edge:    &PrivateEdge{},
		},
		{
			name: "Tlv payload private edge",
			hop: route.Hop{
				AmtToForward:     1000,
				OutgoingTimeLock: 600000,
				ChannelID:        3432483437438,
				LegacyPayload:    false,
			},
			nextHop: 1,
			edge:    &PrivateEdge{},
		},
		{
			name: "Blinded edge",
			hop: route.Hop{
				EncryptedData: []byte{12, 13},
			},
			edge: &BlindedEdge{
				cipherText: []byte{12, 13},
			},
		},
		{
			name: "Blinded edge - introduction point",
			hop: route.Hop{
				EncryptedData: []byte{12, 13},
				BlindingPoint: blindedPoint,
			},
			edge: &BlindedEdge{
				cipherText:    []byte{12, 13},
				blindingPoint: blindedPoint,
			},
		},
	}

	for _, testCase := range testCases {
		testCase := testCase

		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()

			payLoad, err := createHopPayload(
				testCase.hop, testCase.nextHop, false,
			)
			require.NoErrorf(t, err, "failed to create hop payload")

			expectedPayloadSize := testCase.edge.
				IntermediatePayloadSize(
					testCase.hop.AmtToForward,
					testCase.hop.OutgoingTimeLock,
					testCase.hop.LegacyPayload,
					testCase.nextHop,
				)

			require.Equal(
				t, expectedPayloadSize,
				uint64(payLoad.NumBytes()),
			)
		})
	}
}

// createHopPayload creates the hop payload of the sphinx package to facilitate
// the testing of the payload size.
func createHopPayload(hop route.Hop, nextHop uint64,
	finalHop bool) (sphinx.HopPayload, error) {

	// If this is the legacy payload, then we can just include the
	// hop data as normal.
	if hop.LegacyPayload {
		// Before we encode this value, we'll pack the next hop
		// into the NextAddress field of the hop info to ensure
		// we point to the right now.
		hopData := sphinx.HopData{
			ForwardAmount: uint64(hop.AmtToForward),
			OutgoingCltv:  hop.OutgoingTimeLock,
		}
		binary.BigEndian.PutUint64(
			hopData.NextAddress[:], nextHop,
		)

		return sphinx.NewLegacyHopPayload(&hopData)
	}

	// For non-legacy payloads, we'll need to pack the
	// routing information, along with any extra TLV
	// information into the new per-hop payload format.
	// We'll also pass in the chan ID of the hop this
	// channel should be forwarded to so we can construct a
	// valid payload.
	var b bytes.Buffer
	err := hop.PackHopPayload(&b, nextHop, finalHop)
	if err != nil {
		return sphinx.HopPayload{}, err
	}

	return sphinx.NewTLVHopPayload(b.Bytes())
}
