package itest

import (
	"bytes"
	"encoding/hex"

	"github.com/btcsuite/btcd/btcutil"
	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
	"github.com/lightningnetwork/lnd/lntest"
	"github.com/lightningnetwork/lnd/lntypes"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/lightningnetwork/lnd/record"
	"github.com/stretchr/testify/require"
)

func testSingleHopInvoice(ht *lntest.HarnessTest) {
	// Open a channel with 100k satoshis between Alice and Bob with Alice
	// being the sole funder of the channel.
	chanAmt := btcutil.Amount(100000)
	alice, bob := ht.Alice, ht.Bob
	cp := ht.OpenChannel(
		alice, bob, lntest.OpenChannelParams{Amt: chanAmt},
	)

	// assertAmountPaid is a helper closure that asserts the amount paid by
	// Alice and received by Bob are expected.
	assertAmountPaid := func(expected int64) {
		ht.AssertAmountPaid("alice -> bob", alice, cp, expected, 0)
		ht.AssertAmountPaid("bob <- alice", bob, cp, 0, expected)
	}

	// Now that the channel is open, create an invoice for Bob which
	// expects a payment of 1000 satoshis from Alice paid via a particular
	// preimage.
	const paymentAmt = 1000
	preimage := bytes.Repeat([]byte("A"), 32)
	invoice := &lnrpc.Invoice{
		Memo:      "testing",
		RPreimage: preimage,
		Value:     paymentAmt,
	}
	invoiceResp := bob.RPC.AddInvoice(invoice)

	// With the invoice for Bob added, send a payment towards Alice paying
	// to the above generated invoice.
	ht.CompletePaymentRequests(alice, []string{invoiceResp.PaymentRequest})

	// Bob's invoice should now be found and marked as settled.
	dbInvoice := bob.RPC.LookupInvoice(invoiceResp.RHash)
	require.Equal(ht, lnrpc.Invoice_SETTLED, dbInvoice.State,
		"bob's invoice should be marked as settled")

	// With the payment completed all balance related stats should be
	// properly updated.
	assertAmountPaid(paymentAmt)

	// Create another invoice for Bob, this time leaving off the preimage
	// to one will be randomly generated. We'll test the proper
	// encoding/decoding of the zpay32 payment requests.
	invoice = &lnrpc.Invoice{
		Memo:  "test3",
		Value: paymentAmt,
	}
	invoiceResp = bob.RPC.AddInvoice(invoice)

	// Next send another payment, but this time using a zpay32 encoded
	// invoice rather than manually specifying the payment details.
	ht.CompletePaymentRequests(alice, []string{invoiceResp.PaymentRequest})

	// The second payment should also have succeeded, with the balances
	// being update accordingly.
	assertAmountPaid(paymentAmt * 2)

	// Next send a keysend payment.
	keySendPreimage := lntypes.Preimage{3, 4, 5, 11}
	keySendHash := keySendPreimage.Hash()

	req := &routerrpc.SendPaymentRequest{
		Dest:           bob.PubKey[:],
		Amt:            paymentAmt,
		FinalCltvDelta: 40,
		PaymentHash:    keySendHash[:],
		DestCustomRecords: map[uint64][]byte{
			record.KeySendType: keySendPreimage[:],
		},
		TimeoutSeconds: 60,
		FeeLimitMsat:   noFeeLimitMsat,
	}
	ht.SendPaymentAssertSettled(alice, req)

	// The keysend payment should also have succeeded, with the balances
	// being update accordingly.
	assertAmountPaid(paymentAmt * 3)

	// Assert that the invoice has the proper AMP fields set, since the
	// legacy keysend payment should have been promoted into an AMP payment
	// internally.
	keysendInvoice := bob.RPC.LookupInvoice(keySendHash[:])
	require.Len(ht, keysendInvoice.Htlcs, 1)
	htlc := keysendInvoice.Htlcs[0]
	require.Zero(ht, htlc.MppTotalAmtMsat)
	require.Nil(ht, htlc.Amp)

	// Now create an invoice and specify routing hints.
	// We will test that the routing hints are encoded properly.
	hintChannel := lnwire.ShortChannelID{BlockHeight: 10}
	bobPubKey := hex.EncodeToString(bob.PubKey[:])
	hint := &lnrpc.HopHint{
		NodeId:                    bobPubKey,
		ChanId:                    hintChannel.ToUint64(),
		FeeBaseMsat:               1,
		FeeProportionalMillionths: 1000000,
		CltvExpiryDelta:           20,
	}
	hints := []*lnrpc.RouteHint{{HopHints: []*lnrpc.HopHint{hint}}}

	invoice = &lnrpc.Invoice{
		Memo:       "hints",
		Value:      paymentAmt,
		RouteHints: hints,
	}

	invoiceResp = bob.RPC.AddInvoice(invoice)
	payreq := bob.RPC.DecodePayReq(invoiceResp.PaymentRequest)
	require.Len(ht, payreq.RouteHints, 1, "expected one routing hint")
	routingHint := payreq.RouteHints[0]
	require.Len(ht, routingHint.HopHints, 1, "expected one hop hint")

	hopHint := routingHint.HopHints[0]
	require.EqualValues(ht, 1000000, hopHint.FeeProportionalMillionths,
		"wrong FeeProportionalMillionths")
	require.Equal(ht, bobPubKey, hopHint.NodeId, "wrong NodeId")
	require.Equal(ht, hintChannel.ToUint64(), hopHint.ChanId,
		"wrong ChanId")
	require.EqualValues(ht, 1, hopHint.FeeBaseMsat, "wrong FeeBaseMsat")
	require.EqualValues(ht, 20, hopHint.CltvExpiryDelta,
		"wrong CltvExpiryDelta")

	ht.CloseChannel(alice, cp)
}