package channeldb

import (
	"crypto/rand"
	"fmt"
	"math"
	"testing"
	"time"

	"github.com/lightningnetwork/lnd/lntypes"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/lightningnetwork/lnd/record"
	"github.com/stretchr/testify/require"
)

var (
	emptyFeatures = lnwire.NewFeatureVector(nil, lnwire.Features)
	testNow       = time.Unix(1, 0)
)

func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) {
	var (
		pre     lntypes.Preimage
		payAddr [32]byte
	)
	if _, err := rand.Read(pre[:]); err != nil {
		return nil, err
	}
	if _, err := rand.Read(payAddr[:]); err != nil {
		return nil, err
	}

	i := &Invoice{
		CreationDate: testNow,
		Terms: ContractTerm{
			Expiry:          4000,
			PaymentPreimage: &pre,
			PaymentAddr:     payAddr,
			Value:           value,
			Features:        emptyFeatures,
		},
		Htlcs: map[CircuitKey]*InvoiceHTLC{},
	}
	i.Memo = []byte("memo")

	// Create a random byte slice of MaxPaymentRequestSize bytes to be used
	// as a dummy paymentrequest, and  determine if it should be set based
	// on one of the random bytes.
	var r [MaxPaymentRequestSize]byte
	if _, err := rand.Read(r[:]); err != nil {
		return nil, err
	}
	if r[0]&1 == 0 {
		i.PaymentRequest = r[:]
	} else {
		i.PaymentRequest = []byte("")
	}

	return i, nil
}

// settleTestInvoice settles a test invoice.
func settleTestInvoice(invoice *Invoice, settleIndex uint64) {
	invoice.SettleDate = testNow
	invoice.AmtPaid = invoice.Terms.Value
	invoice.State = ContractSettled
	invoice.Htlcs[CircuitKey{}] = &InvoiceHTLC{
		Amt:           invoice.Terms.Value,
		AcceptTime:    testNow,
		ResolveTime:   testNow,
		State:         HtlcStateSettled,
		CustomRecords: make(record.CustomSet),
	}
	invoice.SettleIndex = settleIndex
}

// Tests that pending invoices are those which are either in ContractOpen or
// in ContractAccepted state.
func TestInvoiceIsPending(t *testing.T) {
	contractStates := []ContractState{
		ContractOpen, ContractSettled, ContractCanceled, ContractAccepted,
	}

	for _, state := range contractStates {
		invoice := Invoice{
			State: state,
		}

		// We expect that an invoice is pending if it's either in ContractOpen
		// or ContractAccepted state.
		pending := (state == ContractOpen || state == ContractAccepted)

		if invoice.IsPending() != pending {
			t.Fatalf("expected pending: %v, got: %v, invoice: %v",
				pending, invoice.IsPending(), invoice)
		}
	}
}

type invWorkflowTest struct {
	name         string
	queryPayHash bool
	queryPayAddr bool
}

var invWorkflowTests = []invWorkflowTest{
	{
		name:         "unknown",
		queryPayHash: false,
		queryPayAddr: false,
	},
	{
		name:         "only payhash known",
		queryPayHash: true,
		queryPayAddr: false,
	},
	{
		name:         "payaddr and payhash known",
		queryPayHash: true,
		queryPayAddr: true,
	},
}

// TestInvoiceWorkflow asserts the basic process of inserting, fetching, and
// updating an invoice. We assert that the flow is successful using when
// querying with various combinations of payment hash and payment address.
func TestInvoiceWorkflow(t *testing.T) {
	t.Parallel()

	for _, test := range invWorkflowTests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			testInvoiceWorkflow(t, test)
		})
	}
}

func testInvoiceWorkflow(t *testing.T, test invWorkflowTest) {
	db, cleanUp, err := MakeTestDB()
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to make test db: %v", err)
	}

	// Create a fake invoice which we'll use several times in the tests
	// below.
	fakeInvoice, err := randInvoice(10000)
	if err != nil {
		t.Fatalf("unable to create invoice: %v", err)
	}
	invPayHash := fakeInvoice.Terms.PaymentPreimage.Hash()

	// Select the payment hash and payment address we will use to lookup or
	// update the invoice for the remainder of the test.
	var (
		payHash lntypes.Hash
		payAddr *[32]byte
		ref     InvoiceRef
	)
	switch {
	case test.queryPayHash && test.queryPayAddr:
		payHash = invPayHash
		payAddr = &fakeInvoice.Terms.PaymentAddr
		ref = InvoiceRefByHashAndAddr(payHash, *payAddr)
	case test.queryPayHash:
		payHash = invPayHash
		ref = InvoiceRefByHash(payHash)
	}

	// Add the invoice to the database, this should succeed as there aren't
	// any existing invoices within the database with the same payment
	// hash.
	if _, err := db.AddInvoice(fakeInvoice, invPayHash); err != nil {
		t.Fatalf("unable to find invoice: %v", err)
	}

	// Attempt to retrieve the invoice which was just added to the
	// database. It should be found, and the invoice returned should be
	// identical to the one created above.
	dbInvoice, err := db.LookupInvoice(ref)
	if !test.queryPayAddr && !test.queryPayHash {
		if err != ErrInvoiceNotFound {
			t.Fatalf("invoice should not exist: %v", err)
		}
		return
	}

	require.Equal(t,
		*fakeInvoice, dbInvoice,
		"invoice fetched from db doesn't match original",
	)

	// The add index of the invoice retrieved from the database should now
	// be fully populated. As this is the first index written to the DB,
	// the addIndex should be 1.
	if dbInvoice.AddIndex != 1 {
		t.Fatalf("wrong add index: expected %v, got %v", 1,
			dbInvoice.AddIndex)
	}

	// Settle the invoice, the version retrieved from the database should
	// now have the settled bit toggle to true and a non-default
	// SettledDate
	payAmt := fakeInvoice.Terms.Value * 2
	_, err = db.UpdateInvoice(ref, getUpdateInvoice(payAmt))
	if err != nil {
		t.Fatalf("unable to settle invoice: %v", err)
	}
	dbInvoice2, err := db.LookupInvoice(ref)
	if err != nil {
		t.Fatalf("unable to fetch invoice: %v", err)
	}
	if dbInvoice2.State != ContractSettled {
		t.Fatalf("invoice should now be settled but isn't")
	}
	if dbInvoice2.SettleDate.IsZero() {
		t.Fatalf("invoice should have non-zero SettledDate but isn't")
	}

	// Our 2x payment should be reflected, and also the settle index of 1
	// should also have been committed for this index.
	if dbInvoice2.AmtPaid != payAmt {
		t.Fatalf("wrong amt paid: expected %v, got %v", payAmt,
			dbInvoice2.AmtPaid)
	}
	if dbInvoice2.SettleIndex != 1 {
		t.Fatalf("wrong settle index: expected %v, got %v", 1,
			dbInvoice2.SettleIndex)
	}

	// Attempt to insert generated above again, this should fail as
	// duplicates are rejected by the processing logic.
	if _, err := db.AddInvoice(fakeInvoice, payHash); err != ErrDuplicateInvoice {
		t.Fatalf("invoice insertion should fail due to duplication, "+
			"instead %v", err)
	}

	// Attempt to look up a non-existent invoice, this should also fail but
	// with a "not found" error.
	var fakeHash [32]byte
	fakeRef := InvoiceRefByHash(fakeHash)
	_, err = db.LookupInvoice(fakeRef)
	if err != ErrInvoiceNotFound {
		t.Fatalf("lookup should have failed, instead %v", err)
	}

	// Add 10 random invoices.
	const numInvoices = 10
	amt := lnwire.NewMSatFromSatoshis(1000)
	invoices := make([]*Invoice, numInvoices+1)
	invoices[0] = &dbInvoice2
	for i := 1; i < len(invoices); i++ {
		invoice, err := randInvoice(amt)
		if err != nil {
			t.Fatalf("unable to create invoice: %v", err)
		}

		hash := invoice.Terms.PaymentPreimage.Hash()
		if _, err := db.AddInvoice(invoice, hash); err != nil {
			t.Fatalf("unable to add invoice %v", err)
		}

		invoices[i] = invoice
	}

	// Perform a scan to collect all the active invoices.
	query := InvoiceQuery{
		IndexOffset:    0,
		NumMaxInvoices: math.MaxUint64,
		PendingOnly:    false,
	}

	response, err := db.QueryInvoices(query)
	if err != nil {
		t.Fatalf("invoice query failed: %v", err)
	}

	// The retrieve list of invoices should be identical as since we're
	// using big endian, the invoices should be retrieved in ascending
	// order (and the primary key should be incremented with each
	// insertion).
	for i := 0; i < len(invoices); i++ {
		require.Equal(t,
			*invoices[i], response.Invoices[i],
			"retrieved invoice doesn't match",
		)
	}
}

// TestAddDuplicatePayAddr asserts that the payment addresses of inserted
// invoices are unique.
func TestAddDuplicatePayAddr(t *testing.T) {
	db, cleanUp, err := MakeTestDB()
	defer cleanUp()
	require.NoError(t, err)

	// Create two invoices with the same payment addr.
	invoice1, err := randInvoice(1000)
	require.NoError(t, err)

	invoice2, err := randInvoice(20000)
	require.NoError(t, err)
	invoice2.Terms.PaymentAddr = invoice1.Terms.PaymentAddr

	// First insert should succeed.
	inv1Hash := invoice1.Terms.PaymentPreimage.Hash()
	_, err = db.AddInvoice(invoice1, inv1Hash)
	require.NoError(t, err)

	// Second insert should fail with duplicate payment addr.
	inv2Hash := invoice2.Terms.PaymentPreimage.Hash()
	_, err = db.AddInvoice(invoice2, inv2Hash)
	require.Error(t, err, ErrDuplicatePayAddr)
}

// TestAddDuplicateKeysendPayAddr asserts that we permit duplicate payment
// addresses to be inserted if they are blank to support JIT legacy keysend
// invoices.
func TestAddDuplicateKeysendPayAddr(t *testing.T) {
	db, cleanUp, err := MakeTestDB()
	defer cleanUp()
	require.NoError(t, err)

	// Create two invoices with the same _blank_ payment addr.
	invoice1, err := randInvoice(1000)
	require.NoError(t, err)
	invoice1.Terms.PaymentAddr = BlankPayAddr

	invoice2, err := randInvoice(20000)
	require.NoError(t, err)
	invoice2.Terms.PaymentAddr = BlankPayAddr

	// Inserting both should succeed without a duplicate payment address
	// failure.
	inv1Hash := invoice1.Terms.PaymentPreimage.Hash()
	_, err = db.AddInvoice(invoice1, inv1Hash)
	require.NoError(t, err)

	inv2Hash := invoice2.Terms.PaymentPreimage.Hash()
	_, err = db.AddInvoice(invoice2, inv2Hash)
	require.NoError(t, err)

	// Querying for each should succeed. Here we use hash+addr refs since
	// the lookup will fail if the hash and addr point to different
	// invoices, so if both succeed we can be assured they aren't included
	// in the payment address index.
	ref1 := InvoiceRefByHashAndAddr(inv1Hash, BlankPayAddr)
	dbInv1, err := db.LookupInvoice(ref1)
	require.NoError(t, err)
	require.Equal(t, invoice1, &dbInv1)

	ref2 := InvoiceRefByHashAndAddr(inv2Hash, BlankPayAddr)
	dbInv2, err := db.LookupInvoice(ref2)
	require.NoError(t, err)
	require.Equal(t, invoice2, &dbInv2)
}

// TestInvRefEquivocation asserts that retrieving or updating an invoice using
// an equivocating InvoiceRef results in ErrInvRefEquivocation.
func TestInvRefEquivocation(t *testing.T) {
	db, cleanUp, err := MakeTestDB()
	defer cleanUp()
	require.NoError(t, err)

	// Add two random invoices.
	invoice1, err := randInvoice(1000)
	require.NoError(t, err)

	inv1Hash := invoice1.Terms.PaymentPreimage.Hash()
	_, err = db.AddInvoice(invoice1, inv1Hash)
	require.NoError(t, err)

	invoice2, err := randInvoice(2000)
	require.NoError(t, err)

	inv2Hash := invoice2.Terms.PaymentPreimage.Hash()
	_, err = db.AddInvoice(invoice2, inv2Hash)
	require.NoError(t, err)

	// Now, query using invoice 1's payment address, but invoice 2's payment
	// hash. We expect an error since the invref points to multiple
	// invoices.
	ref := InvoiceRefByHashAndAddr(inv2Hash, invoice1.Terms.PaymentAddr)
	_, err = db.LookupInvoice(ref)
	require.Error(t, err, ErrInvRefEquivocation)

	// The same error should be returned when updating an equivocating
	// reference.
	nop := func(_ *Invoice) (*InvoiceUpdateDesc, error) {
		return nil, nil
	}
	_, err = db.UpdateInvoice(ref, nop)
	require.Error(t, err, ErrInvRefEquivocation)
}

// TestInvoiceCancelSingleHtlc tests that a single htlc can be canceled on the
// invoice.
func TestInvoiceCancelSingleHtlc(t *testing.T) {
	t.Parallel()

	db, cleanUp, err := MakeTestDB()
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to make test db: %v", err)
	}

	preimage := lntypes.Preimage{1}
	paymentHash := preimage.Hash()

	testInvoice := &Invoice{
		Htlcs: map[CircuitKey]*InvoiceHTLC{},
		Terms: ContractTerm{
			Value:           lnwire.NewMSatFromSatoshis(10000),
			Features:        emptyFeatures,
			PaymentPreimage: &preimage,
		},
	}

	if _, err := db.AddInvoice(testInvoice, paymentHash); err != nil {
		t.Fatalf("unable to find invoice: %v", err)
	}

	// Accept an htlc on this invoice.
	key := CircuitKey{ChanID: lnwire.NewShortChanIDFromInt(1), HtlcID: 4}
	htlc := HtlcAcceptDesc{
		Amt:           500,
		CustomRecords: make(record.CustomSet),
	}

	ref := InvoiceRefByHash(paymentHash)
	invoice, err := db.UpdateInvoice(ref,
		func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
			return &InvoiceUpdateDesc{
				AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{
					key: &htlc,
				},
			}, nil
		})
	if err != nil {
		t.Fatalf("unable to add invoice htlc: %v", err)
	}
	if len(invoice.Htlcs) != 1 {
		t.Fatalf("expected the htlc to be added")
	}
	if invoice.Htlcs[key].State != HtlcStateAccepted {
		t.Fatalf("expected htlc in state accepted")
	}

	// Cancel the htlc again.
	invoice, err = db.UpdateInvoice(ref,
		func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
			return &InvoiceUpdateDesc{
				CancelHtlcs: map[CircuitKey]struct{}{
					key: {},
				},
			}, nil
		})
	if err != nil {
		t.Fatalf("unable to cancel htlc: %v", err)
	}
	if len(invoice.Htlcs) != 1 {
		t.Fatalf("expected the htlc to be present")
	}
	if invoice.Htlcs[key].State != HtlcStateCanceled {
		t.Fatalf("expected htlc in state canceled")
	}
}

// TestInvoiceTimeSeries tests that newly added invoices invoices, as well as
// settled invoices are added to the database are properly placed in the add
// add or settle index which serves as an event time series.
func TestInvoiceAddTimeSeries(t *testing.T) {
	t.Parallel()

	db, cleanUp, err := MakeTestDB(OptionClock(testClock))
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to make test db: %v", err)
	}

	_, err = db.InvoicesAddedSince(0)
	require.NoError(t, err)

	// We'll start off by creating 20 random invoices, and inserting them
	// into the database.
	const numInvoices = 20
	amt := lnwire.NewMSatFromSatoshis(1000)
	invoices := make([]Invoice, numInvoices)
	for i := 0; i < len(invoices); i++ {
		invoice, err := randInvoice(amt)
		if err != nil {
			t.Fatalf("unable to create invoice: %v", err)
		}

		paymentHash := invoice.Terms.PaymentPreimage.Hash()

		if _, err := db.AddInvoice(invoice, paymentHash); err != nil {
			t.Fatalf("unable to add invoice %v", err)
		}

		invoices[i] = *invoice
	}

	// With the invoices constructed, we'll now create a series of queries
	// that we'll use to assert expected return values of
	// InvoicesAddedSince.
	addQueries := []struct {
		sinceAddIndex uint64

		resp []Invoice
	}{
		// If we specify a value of zero, we shouldn't get any invoices
		// back.
		{
			sinceAddIndex: 0,
		},

		// If we specify a value well beyond the number of inserted
		// invoices, we shouldn't get any invoices back.
		{
			sinceAddIndex: 99999999,
		},

		// Using an index of 1 should result in all values, but the
		// first one being returned.
		{
			sinceAddIndex: 1,
			resp:          invoices[1:],
		},

		// If we use an index of 10, then we should retrieve the
		// reaming 10 invoices.
		{
			sinceAddIndex: 10,
			resp:          invoices[10:],
		},
	}

	for i, query := range addQueries {
		resp, err := db.InvoicesAddedSince(query.sinceAddIndex)
		if err != nil {
			t.Fatalf("unable to query: %v", err)
		}

		require.Equal(t, len(query.resp), len(resp))

		for j := 0; j < len(query.resp); j++ {
			require.Equal(t,
				query.resp[j], resp[j],
				fmt.Sprintf("test: #%v, item: #%v", i, j),
			)
		}
	}

	_, err = db.InvoicesSettledSince(0)
	require.NoError(t, err)

	var settledInvoices []Invoice
	var settleIndex uint64 = 1
	// We'll now only settle the latter half of each of those invoices.
	for i := 10; i < len(invoices); i++ {
		invoice := &invoices[i]

		paymentHash := invoice.Terms.PaymentPreimage.Hash()

		ref := InvoiceRefByHash(paymentHash)
		_, err := db.UpdateInvoice(
			ref, getUpdateInvoice(invoice.Terms.Value),
		)
		if err != nil {
			t.Fatalf("unable to settle invoice: %v", err)
		}

		// Create the settled invoice for the expectation set.
		settleTestInvoice(invoice, settleIndex)
		settleIndex++

		settledInvoices = append(settledInvoices, *invoice)
	}

	// We'll now prepare an additional set of queries to ensure the settle
	// time series has properly been maintained in the database.
	settleQueries := []struct {
		sinceSettleIndex uint64

		resp []Invoice
	}{
		// If we specify a value of zero, we shouldn't get any settled
		// invoices back.
		{
			sinceSettleIndex: 0,
		},

		// If we specify a value well beyond the number of settled
		// invoices, we shouldn't get any invoices back.
		{
			sinceSettleIndex: 99999999,
		},

		// Using an index of 1 should result in the final 10 invoices
		// being returned, as we only settled those.
		{
			sinceSettleIndex: 1,
			resp:             settledInvoices[1:],
		},
	}

	for i, query := range settleQueries {
		resp, err := db.InvoicesSettledSince(query.sinceSettleIndex)
		if err != nil {
			t.Fatalf("unable to query: %v", err)
		}

		require.Equal(t, len(query.resp), len(resp))

		for j := 0; j < len(query.resp); j++ {
			require.Equal(t,
				query.resp[j], resp[j],
				fmt.Sprintf("test: #%v, item: #%v", i, j),
			)
		}
	}
}

// TestScanInvoices tests that ScanInvoices scans trough all stored invoices
// correctly.
func TestScanInvoices(t *testing.T) {
	t.Parallel()

	db, cleanup, err := MakeTestDB()
	defer cleanup()
	if err != nil {
		t.Fatalf("unable to make test db: %v", err)
	}

	var invoices map[lntypes.Hash]*Invoice
	callCount := 0
	resetCount := 0

	// reset is used to reset/initialize results and is called once
	// upon calling ScanInvoices and when the underlying transaction is
	// retried.
	reset := func() {
		invoices = make(map[lntypes.Hash]*Invoice)
		callCount = 0
		resetCount++

	}

	scanFunc := func(paymentHash lntypes.Hash, invoice *Invoice) error {
		invoices[paymentHash] = invoice
		callCount++

		return nil
	}

	// With an empty DB we expect to not scan any invoices.
	require.NoError(t, db.ScanInvoices(scanFunc, reset))
	require.Equal(t, 0, len(invoices))
	require.Equal(t, 0, callCount)
	require.Equal(t, 1, resetCount)

	numInvoices := 5
	testInvoices := make(map[lntypes.Hash]*Invoice)

	// Now populate the DB and check if we can get all invoices with their
	// payment hashes as expected.
	for i := 1; i <= numInvoices; i++ {
		invoice, err := randInvoice(lnwire.MilliSatoshi(i))
		require.NoError(t, err)

		paymentHash := invoice.Terms.PaymentPreimage.Hash()
		testInvoices[paymentHash] = invoice

		_, err = db.AddInvoice(invoice, paymentHash)
		require.NoError(t, err)
	}

	resetCount = 0
	require.NoError(t, db.ScanInvoices(scanFunc, reset))
	require.Equal(t, numInvoices, callCount)
	require.Equal(t, testInvoices, invoices)
	require.Equal(t, 1, resetCount)
}

// TestDuplicateSettleInvoice tests that if we add a new invoice and settle it
// twice, then the second time we also receive the invoice that we settled as a
// return argument.
func TestDuplicateSettleInvoice(t *testing.T) {
	t.Parallel()

	db, cleanUp, err := MakeTestDB(OptionClock(testClock))
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to make test db: %v", err)
	}

	// We'll start out by creating an invoice and writing it to the DB.
	amt := lnwire.NewMSatFromSatoshis(1000)
	invoice, err := randInvoice(amt)
	if err != nil {
		t.Fatalf("unable to create invoice: %v", err)
	}

	payHash := invoice.Terms.PaymentPreimage.Hash()

	if _, err := db.AddInvoice(invoice, payHash); err != nil {
		t.Fatalf("unable to add invoice %v", err)
	}

	// With the invoice in the DB, we'll now attempt to settle the invoice.
	ref := InvoiceRefByHash(payHash)
	dbInvoice, err := db.UpdateInvoice(ref, getUpdateInvoice(amt))
	if err != nil {
		t.Fatalf("unable to settle invoice: %v", err)
	}

	// We'll update what we expect the settle invoice to be so that our
	// comparison below has the correct assumption.
	invoice.SettleIndex = 1
	invoice.State = ContractSettled
	invoice.AmtPaid = amt
	invoice.SettleDate = dbInvoice.SettleDate
	invoice.Htlcs = map[CircuitKey]*InvoiceHTLC{
		{}: {
			Amt:           amt,
			AcceptTime:    time.Unix(1, 0),
			ResolveTime:   time.Unix(1, 0),
			State:         HtlcStateSettled,
			CustomRecords: make(record.CustomSet),
		},
	}

	// We should get back the exact same invoice that we just inserted.
	require.Equal(t, invoice, dbInvoice, "wrong invoice after settle")

	// If we try to settle the invoice again, then we should get the very
	// same invoice back, but with an error this time.
	dbInvoice, err = db.UpdateInvoice(ref, getUpdateInvoice(amt))
	if err != ErrInvoiceAlreadySettled {
		t.Fatalf("expected ErrInvoiceAlreadySettled")
	}

	if dbInvoice == nil {
		t.Fatalf("invoice from db is nil after settle!")
	}

	invoice.SettleDate = dbInvoice.SettleDate
	require.Equal(t, invoice, dbInvoice, "wrong invoice after second settle")
}

// TestQueryInvoices ensures that we can properly query the invoice database for
// invoices using different types of queries.
func TestQueryInvoices(t *testing.T) {
	t.Parallel()

	db, cleanUp, err := MakeTestDB(OptionClock(testClock))
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to make test db: %v", err)
	}

	// To begin the test, we'll add 50 invoices to the database. We'll
	// assume that the index of the invoice within the database is the same
	// as the amount of the invoice itself.
	const numInvoices = 50
	var settleIndex uint64 = 1
	var invoices []Invoice
	var pendingInvoices []Invoice

	for i := 1; i <= numInvoices; i++ {
		amt := lnwire.MilliSatoshi(i)
		invoice, err := randInvoice(amt)
		if err != nil {
			t.Fatalf("unable to create invoice: %v", err)
		}

		paymentHash := invoice.Terms.PaymentPreimage.Hash()

		if _, err := db.AddInvoice(invoice, paymentHash); err != nil {
			t.Fatalf("unable to add invoice: %v", err)
		}

		// We'll only settle half of all invoices created.
		if i%2 == 0 {
			ref := InvoiceRefByHash(paymentHash)
			_, err := db.UpdateInvoice(ref, getUpdateInvoice(amt))
			if err != nil {
				t.Fatalf("unable to settle invoice: %v", err)
			}

			// Create the settled invoice for the expectation set.
			settleTestInvoice(invoice, settleIndex)
			settleIndex++
		} else {
			pendingInvoices = append(pendingInvoices, *invoice)
		}

		invoices = append(invoices, *invoice)
	}

	// The test will consist of several queries along with their respective
	// expected response. Each query response should match its expected one.
	testCases := []struct {
		query    InvoiceQuery
		expected []Invoice
	}{
		// Fetch all invoices with a single query.
		{
			query: InvoiceQuery{
				NumMaxInvoices: numInvoices,
			},
			expected: invoices,
		},
		// Fetch all invoices with a single query, reversed.
		{
			query: InvoiceQuery{
				Reversed:       true,
				NumMaxInvoices: numInvoices,
			},
			expected: invoices,
		},
		// Fetch the first 25 invoices.
		{
			query: InvoiceQuery{
				NumMaxInvoices: numInvoices / 2,
			},
			expected: invoices[:numInvoices/2],
		},
		// Fetch the first 10 invoices, but this time iterating
		// backwards.
		{
			query: InvoiceQuery{
				IndexOffset:    11,
				Reversed:       true,
				NumMaxInvoices: numInvoices,
			},
			expected: invoices[:10],
		},
		// Fetch the last 40 invoices.
		{
			query: InvoiceQuery{
				IndexOffset:    10,
				NumMaxInvoices: numInvoices,
			},
			expected: invoices[10:],
		},
		// Fetch all but the first invoice.
		{
			query: InvoiceQuery{
				IndexOffset:    1,
				NumMaxInvoices: numInvoices,
			},
			expected: invoices[1:],
		},
		// Fetch one invoice, reversed, with index offset 3. This
		// should give us the second invoice in the array.
		{
			query: InvoiceQuery{
				IndexOffset:    3,
				Reversed:       true,
				NumMaxInvoices: 1,
			},
			expected: invoices[1:2],
		},
		// Same as above, at index 2.
		{
			query: InvoiceQuery{
				IndexOffset:    2,
				Reversed:       true,
				NumMaxInvoices: 1,
			},
			expected: invoices[0:1],
		},
		// Fetch one invoice, at index 1, reversed. Since invoice#1 is
		// the very first, there won't be any left in a reverse search,
		// so we expect no invoices to be returned.
		{
			query: InvoiceQuery{
				IndexOffset:    1,
				Reversed:       true,
				NumMaxInvoices: 1,
			},
			expected: nil,
		},
		// Same as above, but don't restrict the number of invoices to
		// 1.
		{
			query: InvoiceQuery{
				IndexOffset:    1,
				Reversed:       true,
				NumMaxInvoices: numInvoices,
			},
			expected: nil,
		},
		// Fetch one invoice, reversed, with no offset set. We expect
		// the last invoice in the response.
		{
			query: InvoiceQuery{
				Reversed:       true,
				NumMaxInvoices: 1,
			},
			expected: invoices[numInvoices-1:],
		},
		// Fetch one invoice, reversed, the offset set at numInvoices+1.
		// We expect this to return the last invoice.
		{
			query: InvoiceQuery{
				IndexOffset:    numInvoices + 1,
				Reversed:       true,
				NumMaxInvoices: 1,
			},
			expected: invoices[numInvoices-1:],
		},
		// Same as above, at offset numInvoices.
		{
			query: InvoiceQuery{
				IndexOffset:    numInvoices,
				Reversed:       true,
				NumMaxInvoices: 1,
			},
			expected: invoices[numInvoices-2 : numInvoices-1],
		},
		// Fetch one invoice, at no offset (same as offset 0). We
		// expect the first invoice only in the response.
		{
			query: InvoiceQuery{
				NumMaxInvoices: 1,
			},
			expected: invoices[:1],
		},
		// Same as above, at offset 1.
		{
			query: InvoiceQuery{
				IndexOffset:    1,
				NumMaxInvoices: 1,
			},
			expected: invoices[1:2],
		},
		// Same as above, at offset 2.
		{
			query: InvoiceQuery{
				IndexOffset:    2,
				NumMaxInvoices: 1,
			},
			expected: invoices[2:3],
		},
		// Same as above, at offset numInvoices-1. Expect the last
		// invoice to be returned.
		{
			query: InvoiceQuery{
				IndexOffset:    numInvoices - 1,
				NumMaxInvoices: 1,
			},
			expected: invoices[numInvoices-1:],
		},
		// Same as above, at offset numInvoices. No invoices should be
		// returned, as there are no invoices after this offset.
		{
			query: InvoiceQuery{
				IndexOffset:    numInvoices,
				NumMaxInvoices: 1,
			},
			expected: nil,
		},
		// Fetch all pending invoices with a single query.
		{
			query: InvoiceQuery{
				PendingOnly:    true,
				NumMaxInvoices: numInvoices,
			},
			expected: pendingInvoices,
		},
		// Fetch the first 12 pending invoices.
		{
			query: InvoiceQuery{
				PendingOnly:    true,
				NumMaxInvoices: numInvoices / 4,
			},
			expected: pendingInvoices[:len(pendingInvoices)/2],
		},
		// Fetch the first 5 pending invoices, but this time iterating
		// backwards.
		{
			query: InvoiceQuery{
				IndexOffset:    10,
				PendingOnly:    true,
				Reversed:       true,
				NumMaxInvoices: numInvoices,
			},
			// Since we seek to the invoice with index 10 and
			// iterate backwards, there should only be 5 pending
			// invoices before it as every other invoice within the
			// index is settled.
			expected: pendingInvoices[:5],
		},
		// Fetch the last 15 invoices.
		{
			query: InvoiceQuery{
				IndexOffset:    20,
				PendingOnly:    true,
				NumMaxInvoices: numInvoices,
			},
			// Since we seek to the invoice with index 20, there are
			// 30 invoices left. From these 30, only 15 of them are
			// still pending.
			expected: pendingInvoices[len(pendingInvoices)-15:],
		},
		// Fetch all invoices paginating backwards, with an index offset
		// that is beyond our last offset. We expect all invoices to be
		// returned.
		{
			query: InvoiceQuery{
				IndexOffset:    numInvoices * 2,
				PendingOnly:    false,
				Reversed:       true,
				NumMaxInvoices: numInvoices,
			},
			expected: invoices,
		},
	}

	for i, testCase := range testCases {
		response, err := db.QueryInvoices(testCase.query)
		if err != nil {
			t.Fatalf("unable to query invoice database: %v", err)
		}

		require.Equal(t, len(testCase.expected), len(response.Invoices))

		for j, expected := range testCase.expected {
			require.Equal(t,
				expected, response.Invoices[j],
				fmt.Sprintf("test: #%v, item: #%v", i, j),
			)
		}
	}
}

// getUpdateInvoice returns an invoice update callback that, when called,
// settles the invoice with the given amount.
func getUpdateInvoice(amt lnwire.MilliSatoshi) InvoiceUpdateCallback {
	return func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
		if invoice.State == ContractSettled {
			return nil, ErrInvoiceAlreadySettled
		}

		noRecords := make(record.CustomSet)

		update := &InvoiceUpdateDesc{
			State: &InvoiceStateUpdateDesc{
				Preimage: invoice.Terms.PaymentPreimage,
				NewState: ContractSettled,
			},
			AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{
				{}: {
					Amt:           amt,
					CustomRecords: noRecords,
				},
			},
		}

		return update, nil
	}
}

// TestCustomRecords tests that custom records are properly recorded in the
// invoice database.
func TestCustomRecords(t *testing.T) {
	t.Parallel()

	db, cleanUp, err := MakeTestDB()
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to make test db: %v", err)
	}

	preimage := lntypes.Preimage{1}
	paymentHash := preimage.Hash()

	testInvoice := &Invoice{
		Htlcs: map[CircuitKey]*InvoiceHTLC{},
		Terms: ContractTerm{
			Value:           lnwire.NewMSatFromSatoshis(10000),
			Features:        emptyFeatures,
			PaymentPreimage: &preimage,
		},
	}

	if _, err := db.AddInvoice(testInvoice, paymentHash); err != nil {
		t.Fatalf("unable to add invoice: %v", err)
	}

	// Accept an htlc with custom records on this invoice.
	key := CircuitKey{ChanID: lnwire.NewShortChanIDFromInt(1), HtlcID: 4}

	records := record.CustomSet{
		100000: []byte{},
		100001: []byte{1, 2},
	}

	ref := InvoiceRefByHash(paymentHash)
	_, err = db.UpdateInvoice(ref,
		func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
			return &InvoiceUpdateDesc{
				AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{
					key: {
						Amt:           500,
						CustomRecords: records,
					},
				},
			}, nil
		},
	)
	if err != nil {
		t.Fatalf("unable to add invoice htlc: %v", err)
	}

	// Retrieve the invoice from that database and verify that the custom
	// records are present.
	dbInvoice, err := db.LookupInvoice(ref)
	if err != nil {
		t.Fatalf("unable to lookup invoice: %v", err)
	}

	if len(dbInvoice.Htlcs) != 1 {
		t.Fatalf("expected the htlc to be added")
	}

	require.Equal(t,
		records, dbInvoice.Htlcs[key].CustomRecords,
		"invalid custom records",
	)
}

// TestInvoiceRef asserts that the proper identifiers are returned from an
// InvoiceRef depending on the constructor used.
func TestInvoiceRef(t *testing.T) {
	payHash := lntypes.Hash{0x01}
	payAddr := [32]byte{0x02}

	// An InvoiceRef by hash should return the provided hash and a nil
	// payment addr.
	refByHash := InvoiceRefByHash(payHash)
	require.Equal(t, payHash, refByHash.PayHash())
	require.Equal(t, (*[32]byte)(nil), refByHash.PayAddr())

	// An InvoiceRef by hash and addr should return the payment hash and
	// payment addr passed to the constructor.
	refByHashAndAddr := InvoiceRefByHashAndAddr(payHash, payAddr)
	require.Equal(t, payHash, refByHashAndAddr.PayHash())
	require.Equal(t, &payAddr, refByHashAndAddr.PayAddr())
}

// TestDeleteInvoices tests that deleting a list of invoices will succeed
// if all delete references are valid, or will fail otherwise.
func TestDeleteInvoices(t *testing.T) {
	t.Parallel()

	db, cleanup, err := MakeTestDB()
	defer cleanup()
	require.NoError(t, err, "unable to make test db")

	// Add some invoices to the test db.
	numInvoices := 3
	invoicesToDelete := make([]InvoiceDeleteRef, numInvoices)

	for i := 0; i < numInvoices; i++ {
		invoice, err := randInvoice(lnwire.MilliSatoshi(i + 1))
		require.NoError(t, err)

		paymentHash := invoice.Terms.PaymentPreimage.Hash()
		addIndex, err := db.AddInvoice(invoice, paymentHash)
		require.NoError(t, err)

		// Settle the second invoice.
		if i == 1 {
			invoice, err = db.UpdateInvoice(
				InvoiceRefByHash(paymentHash),
				getUpdateInvoice(invoice.Terms.Value),
			)
			require.NoError(t, err, "unable to settle invoice")
		}

		// store the delete ref for later.
		invoicesToDelete[i] = InvoiceDeleteRef{
			PayHash:     paymentHash,
			PayAddr:     &invoice.Terms.PaymentAddr,
			AddIndex:    addIndex,
			SettleIndex: invoice.SettleIndex,
		}
	}

	// assertInvoiceCount asserts that the number of invoices equals
	// to the passed count.
	assertInvoiceCount := func(count int) {
		// Query to collect all invoices.
		query := InvoiceQuery{
			IndexOffset:    0,
			NumMaxInvoices: math.MaxUint64,
		}

		// Check that we really have 3 invoices.
		response, err := db.QueryInvoices(query)
		require.NoError(t, err)
		require.Equal(t, count, len(response.Invoices))
	}

	// XOR one byte of one of the references' hash and attempt to delete.
	invoicesToDelete[0].PayHash[2] ^= 3
	require.Error(t, db.DeleteInvoice(invoicesToDelete))
	assertInvoiceCount(3)

	// Restore the hash.
	invoicesToDelete[0].PayHash[2] ^= 3

	// XOR one byte of one of the references' payment address and attempt
	// to delete.
	invoicesToDelete[1].PayAddr[5] ^= 7
	require.Error(t, db.DeleteInvoice(invoicesToDelete))
	assertInvoiceCount(3)

	// Restore the payment address.
	invoicesToDelete[1].PayAddr[5] ^= 7

	// XOR the second invoice's payment settle index as it is settled, and
	// attempt to delete.
	invoicesToDelete[1].SettleIndex ^= 11
	require.Error(t, db.DeleteInvoice(invoicesToDelete))
	assertInvoiceCount(3)

	// Restore the settle index.
	invoicesToDelete[1].SettleIndex ^= 11

	// XOR the add index for one of the references and attempt to delete.
	invoicesToDelete[2].AddIndex ^= 13
	require.Error(t, db.DeleteInvoice(invoicesToDelete))
	assertInvoiceCount(3)

	// Restore the add index.
	invoicesToDelete[2].AddIndex ^= 13

	// Delete should succeed with all the valid references.
	require.NoError(t, db.DeleteInvoice(invoicesToDelete))
	assertInvoiceCount(0)
}
