package channeldb

import (
	"bytes"
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"time"

	"github.com/lightningnetwork/lnd/channeldb/models"
	"github.com/lightningnetwork/lnd/htlcswitch/hop"
	invpkg "github.com/lightningnetwork/lnd/invoices"
	"github.com/lightningnetwork/lnd/kvdb"
	"github.com/lightningnetwork/lnd/lntypes"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/lightningnetwork/lnd/record"
	"github.com/lightningnetwork/lnd/tlv"
)

var (
	// invoiceBucket is the name of the bucket within the database that
	// stores all data related to invoices no matter their final state.
	// Within the invoice bucket, each invoice is keyed by its invoice ID
	// which is a monotonically increasing uint32.
	invoiceBucket = []byte("invoices")

	// paymentHashIndexBucket is the name of the sub-bucket within the
	// invoiceBucket which indexes all invoices by their payment hash. The
	// payment hash is the sha256 of the invoice's payment preimage. This
	// index is used to detect duplicates, and also to provide a fast path
	// for looking up incoming HTLCs to determine if we're able to settle
	// them fully.
	//
	// maps: payHash => invoiceKey
	invoiceIndexBucket = []byte("paymenthashes")

	// payAddrIndexBucket is the name of the top-level bucket that maps
	// payment addresses to their invoice number. This can be used
	// to efficiently query or update non-legacy invoices. Note that legacy
	// invoices will not be included in this index since they all have the
	// same, all-zero payment address, however all newly generated invoices
	// will end up in this index.
	//
	// maps: payAddr => invoiceKey
	payAddrIndexBucket = []byte("pay-addr-index")

	// setIDIndexBucket is the name of the top-level bucket that maps set
	// ids to their invoice number. This can be used to efficiently query or
	// update AMP invoice. Note that legacy or MPP invoices will not be
	// included in this index, since their HTLCs do not have a set id.
	//
	// maps: setID => invoiceKey
	setIDIndexBucket = []byte("set-id-index")

	// numInvoicesKey is the name of key which houses the auto-incrementing
	// invoice ID which is essentially used as a primary key. With each
	// invoice inserted, the primary key is incremented by one. This key is
	// stored within the invoiceIndexBucket. Within the invoiceBucket
	// invoices are uniquely identified by the invoice ID.
	numInvoicesKey = []byte("nik")

	// addIndexBucket is an index bucket that we'll use to create a
	// monotonically increasing set of add indexes. Each time we add a new
	// invoice, this sequence number will be incremented and then populated
	// within the new invoice.
	//
	// In addition to this sequence number, we map:
	//
	//   addIndexNo => invoiceKey
	addIndexBucket = []byte("invoice-add-index")

	// settleIndexBucket is an index bucket that we'll use to create a
	// monotonically increasing integer for tracking a "settle index". Each
	// time an invoice is settled, this sequence number will be incremented
	// as populate within the newly settled invoice.
	//
	// In addition to this sequence number, we map:
	//
	//   settleIndexNo => invoiceKey
	settleIndexBucket = []byte("invoice-settle-index")
)

const (
	// A set of tlv type definitions used to serialize invoice htlcs to the
	// database.
	//
	// NOTE: A migration should be added whenever this list changes. This
	// prevents against the database being rolled back to an older
	// format where the surrounding logic might assume a different set of
	// fields are known.
	chanIDType       tlv.Type = 1
	htlcIDType       tlv.Type = 3
	amtType          tlv.Type = 5
	acceptHeightType tlv.Type = 7
	acceptTimeType   tlv.Type = 9
	resolveTimeType  tlv.Type = 11
	expiryHeightType tlv.Type = 13
	htlcStateType    tlv.Type = 15
	mppTotalAmtType  tlv.Type = 17
	htlcAMPType      tlv.Type = 19
	htlcHashType     tlv.Type = 21
	htlcPreimageType tlv.Type = 23

	// A set of tlv type definitions used to serialize invoice bodiees.
	//
	// NOTE: A migration should be added whenever this list changes. This
	// prevents against the database being rolled back to an older
	// format where the surrounding logic might assume a different set of
	// fields are known.
	memoType            tlv.Type = 0
	payReqType          tlv.Type = 1
	createTimeType      tlv.Type = 2
	settleTimeType      tlv.Type = 3
	addIndexType        tlv.Type = 4
	settleIndexType     tlv.Type = 5
	preimageType        tlv.Type = 6
	valueType           tlv.Type = 7
	cltvDeltaType       tlv.Type = 8
	expiryType          tlv.Type = 9
	paymentAddrType     tlv.Type = 10
	featuresType        tlv.Type = 11
	invStateType        tlv.Type = 12
	amtPaidType         tlv.Type = 13
	hodlInvoiceType     tlv.Type = 14
	invoiceAmpStateType tlv.Type = 15

	// A set of tlv type definitions used to serialize the invoice AMP
	// state along-side the main invoice body.
	ampStateSetIDType       tlv.Type = 0
	ampStateHtlcStateType   tlv.Type = 1
	ampStateSettleIndexType tlv.Type = 2
	ampStateSettleDateType  tlv.Type = 3
	ampStateCircuitKeysType tlv.Type = 4
	ampStateAmtPaidType     tlv.Type = 5
)

// AddInvoice inserts the targeted invoice into the database. If the invoice has
// *any* payment hashes which already exists within the database, then the
// insertion will be aborted and rejected due to the strict policy banning any
// duplicate payment hashes. A side effect of this function is that it sets
// AddIndex on newInvoice.
func (d *DB) AddInvoice(_ context.Context, newInvoice *invpkg.Invoice,
	paymentHash lntypes.Hash) (uint64, error) {

	if err := invpkg.ValidateInvoice(newInvoice, paymentHash); err != nil {
		return 0, err
	}

	var invoiceAddIndex uint64
	err := kvdb.Update(d, func(tx kvdb.RwTx) error {
		invoices, err := tx.CreateTopLevelBucket(invoiceBucket)
		if err != nil {
			return err
		}

		invoiceIndex, err := invoices.CreateBucketIfNotExists(
			invoiceIndexBucket,
		)
		if err != nil {
			return err
		}
		addIndex, err := invoices.CreateBucketIfNotExists(
			addIndexBucket,
		)
		if err != nil {
			return err
		}

		// Ensure that an invoice an identical payment hash doesn't
		// already exist within the index.
		if invoiceIndex.Get(paymentHash[:]) != nil {
			return invpkg.ErrDuplicateInvoice
		}

		// Check that we aren't inserting an invoice with a duplicate
		// payment address. The all-zeros payment address is
		// special-cased to support legacy keysend invoices which don't
		// assign one. This is safe since later we also will avoid
		// indexing them and avoid collisions.
		payAddrIndex := tx.ReadWriteBucket(payAddrIndexBucket)
		if newInvoice.Terms.PaymentAddr != invpkg.BlankPayAddr {
			paymentAddr := newInvoice.Terms.PaymentAddr[:]
			if payAddrIndex.Get(paymentAddr) != nil {
				return invpkg.ErrDuplicatePayAddr
			}
		}

		// If the current running payment ID counter hasn't yet been
		// created, then create it now.
		var invoiceNum uint32
		invoiceCounter := invoiceIndex.Get(numInvoicesKey)
		if invoiceCounter == nil {
			var scratch [4]byte
			byteOrder.PutUint32(scratch[:], invoiceNum)
			err := invoiceIndex.Put(numInvoicesKey, scratch[:])
			if err != nil {
				return err
			}
		} else {
			invoiceNum = byteOrder.Uint32(invoiceCounter)
		}

		newIndex, err := putInvoice(
			invoices, invoiceIndex, payAddrIndex, addIndex,
			newInvoice, invoiceNum, paymentHash,
		)
		if err != nil {
			return err
		}

		invoiceAddIndex = newIndex
		return nil
	}, func() {
		invoiceAddIndex = 0
	})
	if err != nil {
		return 0, err
	}

	return invoiceAddIndex, err
}

// InvoicesAddedSince can be used by callers to seek into the event time series
// of all the invoices added in the database. The specified sinceAddIndex
// should be the highest add index that the caller knows of. This method will
// return all invoices with an add index greater than the specified
// sinceAddIndex.
//
// NOTE: The index starts from 1, as a result. We enforce that specifying a
// value below the starting index value is a noop.
func (d *DB) InvoicesAddedSince(_ context.Context, sinceAddIndex uint64) (
	[]invpkg.Invoice, error) {

	var newInvoices []invpkg.Invoice

	// If an index of zero was specified, then in order to maintain
	// backwards compat, we won't send out any new invoices.
	if sinceAddIndex == 0 {
		return newInvoices, nil
	}

	var startIndex [8]byte
	byteOrder.PutUint64(startIndex[:], sinceAddIndex)

	err := kvdb.View(d, func(tx kvdb.RTx) error {
		invoices := tx.ReadBucket(invoiceBucket)
		if invoices == nil {
			return nil
		}

		addIndex := invoices.NestedReadBucket(addIndexBucket)
		if addIndex == nil {
			return nil
		}

		// We'll now run through each entry in the add index starting
		// at our starting index. We'll continue until we reach the
		// very end of the current key space.
		invoiceCursor := addIndex.ReadCursor()

		// We'll seek to the starting index, then manually advance the
		// cursor in order to skip the entry with the since add index.
		invoiceCursor.Seek(startIndex[:])
		addSeqNo, invoiceKey := invoiceCursor.Next()

		for ; addSeqNo != nil && bytes.Compare(addSeqNo, startIndex[:]) > 0; addSeqNo, invoiceKey = invoiceCursor.Next() {

			// For each key found, we'll look up the actual
			// invoice, then accumulate it into our return value.
			invoice, err := fetchInvoice(
				invoiceKey, invoices, nil, false,
			)
			if err != nil {
				return err
			}

			newInvoices = append(newInvoices, invoice)
		}

		return nil
	}, func() {
		newInvoices = nil
	})
	if err != nil {
		return nil, err
	}

	return newInvoices, nil
}

// LookupInvoice attempts to look up an invoice according to its 32 byte
// payment hash. If an invoice which can settle the HTLC identified by the
// passed payment hash isn't found, then an error is returned. Otherwise, the
// full invoice is returned. Before setting the incoming HTLC, the values
// SHOULD be checked to ensure the payer meets the agreed upon contractual
// terms of the payment.
func (d *DB) LookupInvoice(_ context.Context, ref invpkg.InvoiceRef) (
	invpkg.Invoice, error) {

	var invoice invpkg.Invoice
	err := kvdb.View(d, func(tx kvdb.RTx) error {
		invoices := tx.ReadBucket(invoiceBucket)
		if invoices == nil {
			return invpkg.ErrNoInvoicesCreated
		}
		invoiceIndex := invoices.NestedReadBucket(invoiceIndexBucket)
		if invoiceIndex == nil {
			return invpkg.ErrNoInvoicesCreated
		}
		payAddrIndex := tx.ReadBucket(payAddrIndexBucket)
		setIDIndex := tx.ReadBucket(setIDIndexBucket)

		// Retrieve the invoice number for this invoice using
		// the provided invoice reference.
		invoiceNum, err := fetchInvoiceNumByRef(
			invoiceIndex, payAddrIndex, setIDIndex, ref,
		)
		if err != nil {
			return err
		}

		var setID *invpkg.SetID
		switch {
		// If this is a payment address ref, and the blank modified was
		// specified, then we'll use the zero set ID to indicate that
		// we won't want any HTLCs returned.
		case ref.PayAddr() != nil &&
			ref.Modifier() == invpkg.HtlcSetBlankModifier:

			var zeroSetID invpkg.SetID
			setID = &zeroSetID

		// If this is a set ID ref, and the htlc set only modified was
		// specified, then we'll pass through the specified setID so
		// only that will be returned.
		case ref.SetID() != nil &&
			ref.Modifier() == invpkg.HtlcSetOnlyModifier:

			setID = (*invpkg.SetID)(ref.SetID())
		}

		// An invoice was found, retrieve the remainder of the invoice
		// body.
		i, err := fetchInvoice(
			invoiceNum, invoices, []*invpkg.SetID{setID}, true,
		)
		if err != nil {
			return err
		}
		invoice = i

		return nil
	}, func() {})
	if err != nil {
		return invoice, err
	}

	return invoice, nil
}

// fetchInvoiceNumByRef retrieve the invoice number for the provided invoice
// reference. The payment address will be treated as the primary key, falling
// back to the payment hash if nothing is found for the payment address. An
// error is returned if the invoice is not found.
func fetchInvoiceNumByRef(invoiceIndex, payAddrIndex, setIDIndex kvdb.RBucket,
	ref invpkg.InvoiceRef) ([]byte, error) {

	// If the set id is present, we only consult the set id index for this
	// invoice. This type of query is only used to facilitate user-facing
	// requests to lookup, settle or cancel an AMP invoice.
	setID := ref.SetID()
	if setID != nil {
		invoiceNumBySetID := setIDIndex.Get(setID[:])
		if invoiceNumBySetID == nil {
			return nil, invpkg.ErrInvoiceNotFound
		}

		return invoiceNumBySetID, nil
	}

	payHash := ref.PayHash()
	payAddr := ref.PayAddr()

	getInvoiceNumByHash := func() []byte {
		if payHash != nil {
			return invoiceIndex.Get(payHash[:])
		}
		return nil
	}

	getInvoiceNumByAddr := func() []byte {
		if payAddr != nil {
			// Only allow lookups for payment address if it is not a
			// blank payment address, which is a special-cased value
			// for legacy keysend invoices.
			if *payAddr != invpkg.BlankPayAddr {
				return payAddrIndex.Get(payAddr[:])
			}
		}
		return nil
	}

	invoiceNumByHash := getInvoiceNumByHash()
	invoiceNumByAddr := getInvoiceNumByAddr()
	switch {
	// If payment address and payment hash both reference an existing
	// invoice, ensure they reference the _same_ invoice.
	case invoiceNumByAddr != nil && invoiceNumByHash != nil:
		if !bytes.Equal(invoiceNumByAddr, invoiceNumByHash) {
			return nil, invpkg.ErrInvRefEquivocation
		}

		return invoiceNumByAddr, nil

	// Return invoices by payment addr only.
	//
	// NOTE: We constrain this lookup to only apply if the invoice ref does
	// not contain a payment hash. Legacy and MPP payments depend on the
	// payment hash index to enforce that the HTLCs payment hash matches the
	// payment hash for the invoice, without this check we would
	// inadvertently assume the invoice contains the correct preimage for
	// the HTLC, which we only enforce via the lookup by the invoice index.
	case invoiceNumByAddr != nil && payHash == nil:
		return invoiceNumByAddr, nil

	// If we were only able to reference the invoice by hash, return the
	// corresponding invoice number. This can happen when no payment address
	// was provided, or if it didn't match anything in our records.
	case invoiceNumByHash != nil:
		return invoiceNumByHash, nil

	// Otherwise we don't know of the target invoice.
	default:
		return nil, invpkg.ErrInvoiceNotFound
	}
}

// FetchPendingInvoices returns all invoices that have not yet been settled or
// canceled. The returned map is keyed by the payment hash of each respective
// invoice.
func (d *DB) FetchPendingInvoices(_ context.Context) (
	map[lntypes.Hash]invpkg.Invoice, error) {

	result := make(map[lntypes.Hash]invpkg.Invoice)

	err := kvdb.View(d, func(tx kvdb.RTx) error {
		invoices := tx.ReadBucket(invoiceBucket)
		if invoices == nil {
			return nil
		}

		invoiceIndex := invoices.NestedReadBucket(invoiceIndexBucket)
		if invoiceIndex == nil {
			// Mask the error if there's no invoice
			// index as that simply means there are no
			// invoices added yet to the DB. In this case
			// we simply return an empty list.
			return nil
		}

		return invoiceIndex.ForEach(func(k, v []byte) error {
			// Skip the special numInvoicesKey as that does not
			// point to a valid invoice.
			if bytes.Equal(k, numInvoicesKey) {
				return nil
			}

			// Skip sub-buckets.
			if v == nil {
				return nil
			}

			invoice, err := fetchInvoice(v, invoices, nil, false)
			if err != nil {
				return err
			}

			if invoice.IsPending() {
				var paymentHash lntypes.Hash
				copy(paymentHash[:], k)
				result[paymentHash] = invoice
			}

			return nil
		})
	}, func() {
		result = make(map[lntypes.Hash]invpkg.Invoice)
	})

	if err != nil {
		return nil, err
	}

	return result, nil
}

// QueryInvoices allows a caller to query the invoice database for invoices
// within the specified add index range.
func (d *DB) QueryInvoices(_ context.Context, q invpkg.InvoiceQuery) (
	invpkg.InvoiceSlice, error) {

	var resp invpkg.InvoiceSlice

	err := kvdb.View(d, func(tx kvdb.RTx) error {
		// If the bucket wasn't found, then there aren't any invoices
		// within the database yet, so we can simply exit.
		invoices := tx.ReadBucket(invoiceBucket)
		if invoices == nil {
			return invpkg.ErrNoInvoicesCreated
		}

		// Get the add index bucket which we will use to iterate through
		// our indexed invoices.
		invoiceAddIndex := invoices.NestedReadBucket(addIndexBucket)
		if invoiceAddIndex == nil {
			return invpkg.ErrNoInvoicesCreated
		}

		// Create a paginator which reads from our add index bucket with
		// the parameters provided by the invoice query.
		paginator := newPaginator(
			invoiceAddIndex.ReadCursor(), q.Reversed, q.IndexOffset,
			q.NumMaxInvoices,
		)

		// accumulateInvoices looks up an invoice based on the index we
		// are given, adds it to our set of invoices if it has the right
		// characteristics for our query and returns the number of items
		// we have added to our set of invoices.
		accumulateInvoices := func(_, indexValue []byte) (bool, error) {
			invoice, err := fetchInvoice(
				indexValue, invoices, nil, false,
			)
			if err != nil {
				return false, err
			}

			// Skip any settled or canceled invoices if the caller
			// is only interested in pending ones.
			if q.PendingOnly && !invoice.IsPending() {
				return false, nil
			}

			// Get the creation time in Unix seconds, this always
			// rounds down the nanoseconds to full seconds.
			createTime := invoice.CreationDate.Unix()

			// Skip any invoices that were created before the
			// specified time.
			if createTime < q.CreationDateStart {
				return false, nil
			}

			// Skip any invoices that were created after the
			// specified time.
			if q.CreationDateEnd != 0 &&
				createTime > q.CreationDateEnd {

				return false, nil
			}

			// At this point, we've exhausted the offset, so we'll
			// begin collecting invoices found within the range.
			resp.Invoices = append(resp.Invoices, invoice)

			return true, nil
		}

		// Query our paginator using accumulateInvoices to build up a
		// set of invoices.
		if err := paginator.query(accumulateInvoices); err != nil {
			return err
		}

		// If we iterated through the add index in reverse order, then
		// we'll need to reverse the slice of invoices to return them in
		// forward order.
		if q.Reversed {
			numInvoices := len(resp.Invoices)
			for i := 0; i < numInvoices/2; i++ {
				reverse := numInvoices - i - 1
				resp.Invoices[i], resp.Invoices[reverse] =
					resp.Invoices[reverse], resp.Invoices[i]
			}
		}

		return nil
	}, func() {
		resp = invpkg.InvoiceSlice{
			InvoiceQuery: q,
		}
	})
	if err != nil && !errors.Is(err, invpkg.ErrNoInvoicesCreated) {
		return resp, err
	}

	// Finally, record the indexes of the first and last invoices returned
	// so that the caller can resume from this point later on.
	if len(resp.Invoices) > 0 {
		resp.FirstIndexOffset = resp.Invoices[0].AddIndex
		lastIdx := len(resp.Invoices) - 1
		resp.LastIndexOffset = resp.Invoices[lastIdx].AddIndex
	}

	return resp, nil
}

// UpdateInvoice attempts to update an invoice corresponding to the passed
// payment hash. If an invoice matching the passed payment hash doesn't exist
// within the database, then the action will fail with a "not found" error.
//
// The update is performed inside the same database transaction that fetches the
// invoice and is therefore atomic. The fields to update are controlled by the
// supplied callback.  When updating an invoice, the update itself happens
// in-memory on a copy of the invoice. Once it is written successfully to the
// database, the in-memory copy is returned to the caller.
func (d *DB) UpdateInvoice(_ context.Context, ref invpkg.InvoiceRef,
	setIDHint *invpkg.SetID, callback invpkg.InvoiceUpdateCallback) (
	*invpkg.Invoice, error) {

	var updatedInvoice *invpkg.Invoice
	err := kvdb.Update(d, func(tx kvdb.RwTx) error {
		invoices, err := tx.CreateTopLevelBucket(invoiceBucket)
		if err != nil {
			return err
		}
		invoiceIndex, err := invoices.CreateBucketIfNotExists(
			invoiceIndexBucket,
		)
		if err != nil {
			return err
		}
		settleIndex, err := invoices.CreateBucketIfNotExists(
			settleIndexBucket,
		)
		if err != nil {
			return err
		}
		payAddrIndex := tx.ReadBucket(payAddrIndexBucket)
		setIDIndex := tx.ReadWriteBucket(setIDIndexBucket)

		// Retrieve the invoice number for this invoice using the
		// provided invoice reference.
		invoiceNum, err := fetchInvoiceNumByRef(
			invoiceIndex, payAddrIndex, setIDIndex, ref,
		)
		if err != nil {
			return err
		}

		// If the set ID hint is non-nil, then we'll use that to filter
		// out the HTLCs for AMP invoice so we don't need to read them
		// all out to satisfy the invoice callback below. If it's nil,
		// then we pass in the zero set ID which means no HTLCs will be
		// read out.
		var invSetID invpkg.SetID

		if setIDHint != nil {
			invSetID = *setIDHint
		}
		invoice, err := fetchInvoice(
			invoiceNum, invoices, []*invpkg.SetID{&invSetID}, false,
		)
		if err != nil {
			return err
		}

		now := d.clock.Now()
		updater := &kvInvoiceUpdater{
			db:                d,
			invoicesBucket:    invoices,
			settleIndexBucket: settleIndex,
			setIDIndexBucket:  setIDIndex,
			updateTime:        now,
			invoiceNum:        invoiceNum,
			invoice:           &invoice,
			updatedAmpHtlcs:   make(ampHTLCsMap),
			settledSetIDs:     make(map[invpkg.SetID]struct{}),
		}

		payHash := ref.PayHash()
		updatedInvoice, err = invpkg.UpdateInvoice(
			payHash, updater.invoice, now, callback, updater,
		)
		if err != nil {
			return err
		}

		// If this is an AMP update, then limit the returned AMP state
		// to only the requested set ID.
		if setIDHint != nil {
			filterInvoiceAMPState(updatedInvoice, &invSetID)
		}

		return nil
	}, func() {
		updatedInvoice = nil
	})

	return updatedInvoice, err
}

// filterInvoiceAMPState filters the AMP state of the invoice to only include
// state for the specified set IDs.
func filterInvoiceAMPState(invoice *invpkg.Invoice, setIDs ...*invpkg.SetID) {
	filteredAMPState := make(invpkg.AMPInvoiceState)

	for _, setID := range setIDs {
		if setID == nil {
			return
		}

		ampState, ok := invoice.AMPState[*setID]
		if ok {
			filteredAMPState[*setID] = ampState
		}
	}

	invoice.AMPState = filteredAMPState
}

// ampHTLCsMap is a map of AMP HTLCs affected by an invoice update.
type ampHTLCsMap map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC

// kvInvoiceUpdater is an implementation of the InvoiceUpdater interface that
// is used with the kv implementation of the invoice database. Note that this
// updater is not concurrency safe and synchronizaton is expected to be handled
// on the DB level.
type kvInvoiceUpdater struct {
	db                *DB
	invoicesBucket    kvdb.RwBucket
	settleIndexBucket kvdb.RwBucket
	setIDIndexBucket  kvdb.RwBucket

	// updateTime is the timestamp for the update.
	updateTime time.Time

	// invoiceNum is a legacy key similar to the add index that is used
	// only in the kv implementation.
	invoiceNum []byte

	// invoice is the invoice that we're updating. As a side effect of the
	// update this invoice will be mutated.
	invoice *invpkg.Invoice

	// updatedAmpHtlcs holds the set of AMP HTLCs that were added or
	// cancelled as part of this update.
	updatedAmpHtlcs ampHTLCsMap

	// settledSetIDs holds the set IDs that are settled with this update.
	settledSetIDs map[invpkg.SetID]struct{}
}

// NOTE: this method does nothing in the k/v implementation of InvoiceUpdater.
func (k *kvInvoiceUpdater) AddHtlc(_ models.CircuitKey,
	_ *invpkg.InvoiceHTLC) error {

	return nil
}

// NOTE: this method does nothing in the k/v implementation of InvoiceUpdater.
func (k *kvInvoiceUpdater) ResolveHtlc(_ models.CircuitKey, _ invpkg.HtlcState,
	_ time.Time) error {

	return nil
}

// NOTE: this method does nothing in the k/v implementation of InvoiceUpdater.
func (k *kvInvoiceUpdater) AddAmpHtlcPreimage(_ [32]byte, _ models.CircuitKey,
	_ lntypes.Preimage) error {

	return nil
}

// NOTE: this method does nothing in the k/v implementation of InvoiceUpdater.
func (k *kvInvoiceUpdater) UpdateInvoiceState(_ invpkg.ContractState,
	_ *lntypes.Preimage) error {

	return nil
}

// NOTE: this method does nothing in the k/v implementation of InvoiceUpdater.
func (k *kvInvoiceUpdater) UpdateInvoiceAmtPaid(_ lnwire.MilliSatoshi) error {
	return nil
}

// UpdateAmpState updates the state of the AMP invoice identified by the setID.
func (k *kvInvoiceUpdater) UpdateAmpState(setID [32]byte,
	state invpkg.InvoiceStateAMP, circuitKey models.CircuitKey) error {

	if _, ok := k.updatedAmpHtlcs[setID]; !ok {
		switch state.State {
		case invpkg.HtlcStateAccepted:
			// If we're just now creating the HTLCs for this set
			// then we'll also pull in the existing HTLCs that are
			// part of this set, so we can write them all to disk
			// together (same value)
			k.updatedAmpHtlcs[setID] = k.invoice.HTLCSet(
				&setID, invpkg.HtlcStateAccepted,
			)

		case invpkg.HtlcStateCanceled:
			// Only HTLCs in the accepted state, can be cancelled,
			// but we also want to merge that with HTLCs that may be
			// canceled as well since it can be cancelled one by
			// one.
			k.updatedAmpHtlcs[setID] = k.invoice.HTLCSet(
				&setID, invpkg.HtlcStateAccepted,
			)

			cancelledHtlcs := k.invoice.HTLCSet(
				&setID, invpkg.HtlcStateCanceled,
			)
			for htlcKey, htlc := range cancelledHtlcs {
				k.updatedAmpHtlcs[setID][htlcKey] = htlc
			}

		case invpkg.HtlcStateSettled:
			k.updatedAmpHtlcs[setID] = make(
				map[models.CircuitKey]*invpkg.InvoiceHTLC,
			)
		}
	}

	if state.State == invpkg.HtlcStateSettled {
		// Add the set ID to the set that was settled in this invoice
		// update. We'll use this later to update the settle index.
		k.settledSetIDs[setID] = struct{}{}
	}

	k.updatedAmpHtlcs[setID][circuitKey] = k.invoice.Htlcs[circuitKey]

	return nil
}

// Finalize finalizes the update before it is written to the database.
func (k *kvInvoiceUpdater) Finalize(updateType invpkg.UpdateType) error {
	switch updateType {
	case invpkg.AddHTLCsUpdate:
		return k.storeAddHtlcsUpdate()

	case invpkg.CancelHTLCsUpdate:
		return k.storeCancelHtlcsUpdate()

	case invpkg.SettleHodlInvoiceUpdate:
		return k.storeSettleHodlInvoiceUpdate()

	case invpkg.CancelInvoiceUpdate:
		return k.serializeAndStoreInvoice()
	}

	return fmt.Errorf("unknown update type: %v", updateType)
}

// storeCancelHtlcsUpdate updates the invoice in the database after cancelling a
// set of HTLCs.
func (k *kvInvoiceUpdater) storeCancelHtlcsUpdate() error {
	err := k.serializeAndStoreInvoice()
	if err != nil {
		return err
	}

	// If this is an AMP invoice, then we'll actually store the rest
	// of the HTLCs in-line with the invoice, using the invoice ID
	// as a prefix, and the AMP key as a suffix: invoiceNum ||
	// setID.
	if k.invoice.IsAMP() {
		return k.updateAMPInvoices()
	}

	return nil
}

// storeAddHtlcsUpdate updates the invoice in the database after adding a set of
// HTLCs.
func (k *kvInvoiceUpdater) storeAddHtlcsUpdate() error {
	invoiceIsAMP := k.invoice.IsAMP()

	for htlcSetID := range k.updatedAmpHtlcs {
		// Check if this SetID already exist.
		setIDInvNum := k.setIDIndexBucket.Get(htlcSetID[:])

		if setIDInvNum == nil {
			err := k.setIDIndexBucket.Put(
				htlcSetID[:], k.invoiceNum,
			)
			if err != nil {
				return err
			}
		} else if !bytes.Equal(setIDInvNum, k.invoiceNum) {
			return invpkg.ErrDuplicateSetID{
				SetID: htlcSetID,
			}
		}
	}

	// If this is a non-AMP invoice, then the state can eventually go to
	// ContractSettled, so we pass in nil value as part of
	// setSettleMetaFields.
	if !invoiceIsAMP && k.invoice.State == invpkg.ContractSettled {
		err := k.setSettleMetaFields(nil)
		if err != nil {
			return err
		}
	}

	// As we don't update the settle index above for AMP invoices, we'll do
	// it here for each sub-AMP invoice that was settled.
	for settledSetID := range k.settledSetIDs {
		settledSetID := settledSetID
		err := k.setSettleMetaFields(&settledSetID)
		if err != nil {
			return err
		}
	}

	err := k.serializeAndStoreInvoice()
	if err != nil {
		return err
	}

	// If this is an AMP invoice, then we'll actually store the rest of the
	// HTLCs in-line with the invoice, using the invoice ID as a prefix,
	// and the AMP key as a suffix: invoiceNum || setID.
	if invoiceIsAMP {
		return k.updateAMPInvoices()
	}

	return nil
}

// storeSettleHodlInvoiceUpdate updates the invoice in the database after
// settling a hodl invoice.
func (k *kvInvoiceUpdater) storeSettleHodlInvoiceUpdate() error {
	err := k.setSettleMetaFields(nil)
	if err != nil {
		return err
	}

	return k.serializeAndStoreInvoice()
}

// setSettleMetaFields updates the metadata associated with settlement of an
// invoice. If a non-nil setID is passed in, then the value will be append to
// the invoice number as well, in order to allow us to detect repeated payments
// to the same AMP invoices "across time".
func (k *kvInvoiceUpdater) setSettleMetaFields(setID *invpkg.SetID) error {
	// Now that we know the invoice hasn't already been settled, we'll
	// update the settle index so we can place this settle event in the
	// proper location within our time series.
	nextSettleSeqNo, err := k.settleIndexBucket.NextSequence()
	if err != nil {
		return err
	}

	// Make a new byte array on the stack that can potentially store the 4
	// byte invoice number along w/ the 32 byte set ID. We capture valueLen
	// here which is the number of bytes copied so we can only store the 4
	// bytes if this is a non-AMP invoice.
	var indexKey [invoiceSetIDKeyLen]byte
	valueLen := copy(indexKey[:], k.invoiceNum)

	if setID != nil {
		valueLen += copy(indexKey[valueLen:], setID[:])
	}

	var seqNoBytes [8]byte
	byteOrder.PutUint64(seqNoBytes[:], nextSettleSeqNo)
	err = k.settleIndexBucket.Put(seqNoBytes[:], indexKey[:valueLen])
	if err != nil {
		return err
	}

	// If the setID is nil, then this means that this is a non-AMP settle,
	// so we'll update the invoice settle index directly.
	if setID == nil {
		k.invoice.SettleDate = k.updateTime
		k.invoice.SettleIndex = nextSettleSeqNo
	} else {
		// If the set ID isn't blank, we'll update the AMP state map
		// which tracks when each of the setIDs associated with a given
		// AMP invoice are settled.
		ampState := k.invoice.AMPState[*setID]

		ampState.SettleDate = k.updateTime
		ampState.SettleIndex = nextSettleSeqNo

		k.invoice.AMPState[*setID] = ampState
	}

	return nil
}

// updateAMPInvoices updates the set of AMP invoices in-place. For AMP, rather
// then continually write the invoices to the end of the invoice value, we
// instead write the invoices into a new key preifx that follows the main
// invoice number. This ensures that we don't need to continually decode a
// potentially massive HTLC set, and also allows us to quickly find the HLTCs
// associated with a particular HTLC set.
func (k *kvInvoiceUpdater) updateAMPInvoices() error {
	for setID, htlcSet := range k.updatedAmpHtlcs {
		// First write out the set of HTLCs including all the relevant
		// TLV values.
		var b bytes.Buffer
		if err := serializeHtlcs(&b, htlcSet); err != nil {
			return err
		}

		// Next store each HTLC in-line, using a prefix based off the
		// invoice number.
		invoiceSetIDKey := makeInvoiceSetIDKey(k.invoiceNum, setID[:])

		err := k.invoicesBucket.Put(invoiceSetIDKey[:], b.Bytes())
		if err != nil {
			return err
		}
	}

	return nil
}

// serializeAndStoreInvoice is a helper function used to store invoices.
func (k *kvInvoiceUpdater) serializeAndStoreInvoice() error {
	var buf bytes.Buffer
	if err := serializeInvoice(&buf, k.invoice); err != nil {
		return err
	}

	return k.invoicesBucket.Put(k.invoiceNum, buf.Bytes())
}

// InvoicesSettledSince can be used by callers to catch up any settled invoices
// they missed within the settled invoice time series. We'll return all known
// settled invoice that have a settle index higher than the passed
// sinceSettleIndex.
//
// NOTE: The index starts from 1, as a result. We enforce that specifying a
// value below the starting index value is a noop.
func (d *DB) InvoicesSettledSince(_ context.Context, sinceSettleIndex uint64) (
	[]invpkg.Invoice, error) {

	var settledInvoices []invpkg.Invoice

	// If an index of zero was specified, then in order to maintain
	// backwards compat, we won't send out any new invoices.
	if sinceSettleIndex == 0 {
		return settledInvoices, nil
	}

	var startIndex [8]byte
	byteOrder.PutUint64(startIndex[:], sinceSettleIndex)

	err := kvdb.View(d, func(tx kvdb.RTx) error {
		invoices := tx.ReadBucket(invoiceBucket)
		if invoices == nil {
			return nil
		}

		settleIndex := invoices.NestedReadBucket(settleIndexBucket)
		if settleIndex == nil {
			return nil
		}

		// We'll now run through each entry in the add index starting
		// at our starting index. We'll continue until we reach the
		// very end of the current key space.
		invoiceCursor := settleIndex.ReadCursor()

		// We'll seek to the starting index, then manually advance the
		// cursor in order to skip the entry with the since add index.
		invoiceCursor.Seek(startIndex[:])
		seqNo, indexValue := invoiceCursor.Next()

		for ; seqNo != nil && bytes.Compare(seqNo, startIndex[:]) > 0; seqNo, indexValue = invoiceCursor.Next() {
			// Depending on the length of the index value, this may
			// or may not be an AMP invoice, so we'll extract the
			// invoice value into two components: the invoice num,
			// and the setID (may not be there).
			var (
				invoiceKey [4]byte
				setID      *invpkg.SetID
			)

			valueLen := copy(invoiceKey[:], indexValue)
			if len(indexValue) == invoiceSetIDKeyLen {
				setID = new(invpkg.SetID)
				copy(setID[:], indexValue[valueLen:])
			}

			// For each key found, we'll look up the actual
			// invoice, then accumulate it into our return value.
			invoice, err := fetchInvoice(
				invoiceKey[:], invoices, []*invpkg.SetID{setID},
				true,
			)
			if err != nil {
				return err
			}

			settledInvoices = append(settledInvoices, invoice)
		}

		return nil
	}, func() {
		settledInvoices = nil
	})
	if err != nil {
		return nil, err
	}

	return settledInvoices, nil
}

func putInvoice(invoices, invoiceIndex, payAddrIndex, addIndex kvdb.RwBucket,
	i *invpkg.Invoice, invoiceNum uint32, paymentHash lntypes.Hash) (
	uint64, error) {

	// Create the invoice key which is just the big-endian representation
	// of the invoice number.
	var invoiceKey [4]byte
	byteOrder.PutUint32(invoiceKey[:], invoiceNum)

	// Increment the num invoice counter index so the next invoice bares
	// the proper ID.
	var scratch [4]byte
	invoiceCounter := invoiceNum + 1
	byteOrder.PutUint32(scratch[:], invoiceCounter)
	if err := invoiceIndex.Put(numInvoicesKey, scratch[:]); err != nil {
		return 0, err
	}

	// Add the payment hash to the invoice index. This will let us quickly
	// identify if we can settle an incoming payment, and also to possibly
	// allow a single invoice to have multiple payment installations.
	err := invoiceIndex.Put(paymentHash[:], invoiceKey[:])
	if err != nil {
		return 0, err
	}

	// Add the invoice to the payment address index, but only if the invoice
	// has a non-zero payment address. The all-zero payment address is still
	// in use by legacy keysend, so we special-case here to avoid
	// collisions.
	if i.Terms.PaymentAddr != invpkg.BlankPayAddr {
		err = payAddrIndex.Put(i.Terms.PaymentAddr[:], invoiceKey[:])
		if err != nil {
			return 0, err
		}
	}

	// Next, we'll obtain the next add invoice index (sequence
	// number), so we can properly place this invoice within this
	// event stream.
	nextAddSeqNo, err := addIndex.NextSequence()
	if err != nil {
		return 0, err
	}

	// With the next sequence obtained, we'll updating the event series in
	// the add index bucket to map this current add counter to the index of
	// this new invoice.
	var seqNoBytes [8]byte
	byteOrder.PutUint64(seqNoBytes[:], nextAddSeqNo)
	if err := addIndex.Put(seqNoBytes[:], invoiceKey[:]); err != nil {
		return 0, err
	}

	i.AddIndex = nextAddSeqNo

	// Finally, serialize the invoice itself to be written to the disk.
	var buf bytes.Buffer
	if err := serializeInvoice(&buf, i); err != nil {
		return 0, err
	}

	if err := invoices.Put(invoiceKey[:], buf.Bytes()); err != nil {
		return 0, err
	}

	return nextAddSeqNo, nil
}

// recordSize returns the amount of bytes this TLV record will occupy when
// encoded.
func ampRecordSize(a *invpkg.AMPInvoiceState) func() uint64 {
	var (
		b   bytes.Buffer
		buf [8]byte
	)

	// We know that encoding works since the tests pass in the build this
	// file is checked into, so we'll simplify things and simply encode it
	// ourselves then report the total amount of bytes used.
	if err := ampStateEncoder(&b, a, &buf); err != nil {
		// This should never error out, but we log it just in case it
		// does.
		log.Errorf("encoding the amp invoice state failed: %v", err)
	}

	return func() uint64 {
		return uint64(len(b.Bytes()))
	}
}

// serializeInvoice serializes an invoice to a writer.
//
// Note: this function is in use for a migration. Before making changes that
// would modify the on disk format, make a copy of the original code and store
// it with the migration.
func serializeInvoice(w io.Writer, i *invpkg.Invoice) error {
	creationDateBytes, err := i.CreationDate.MarshalBinary()
	if err != nil {
		return err
	}

	settleDateBytes, err := i.SettleDate.MarshalBinary()
	if err != nil {
		return err
	}

	var fb bytes.Buffer
	err = i.Terms.Features.EncodeBase256(&fb)
	if err != nil {
		return err
	}
	featureBytes := fb.Bytes()

	preimage := [32]byte(invpkg.UnknownPreimage)
	if i.Terms.PaymentPreimage != nil {
		preimage = *i.Terms.PaymentPreimage
		if preimage == invpkg.UnknownPreimage {
			return errors.New("cannot use all-zeroes preimage")
		}
	}
	value := uint64(i.Terms.Value)
	cltvDelta := uint32(i.Terms.FinalCltvDelta)
	expiry := uint64(i.Terms.Expiry)

	amtPaid := uint64(i.AmtPaid)
	state := uint8(i.State)

	var hodlInvoice uint8
	if i.HodlInvoice {
		hodlInvoice = 1
	}

	tlvStream, err := tlv.NewStream(
		// Memo and payreq.
		tlv.MakePrimitiveRecord(memoType, &i.Memo),
		tlv.MakePrimitiveRecord(payReqType, &i.PaymentRequest),

		// Add/settle metadata.
		tlv.MakePrimitiveRecord(createTimeType, &creationDateBytes),
		tlv.MakePrimitiveRecord(settleTimeType, &settleDateBytes),
		tlv.MakePrimitiveRecord(addIndexType, &i.AddIndex),
		tlv.MakePrimitiveRecord(settleIndexType, &i.SettleIndex),

		// Terms.
		tlv.MakePrimitiveRecord(preimageType, &preimage),
		tlv.MakePrimitiveRecord(valueType, &value),
		tlv.MakePrimitiveRecord(cltvDeltaType, &cltvDelta),
		tlv.MakePrimitiveRecord(expiryType, &expiry),
		tlv.MakePrimitiveRecord(paymentAddrType, &i.Terms.PaymentAddr),
		tlv.MakePrimitiveRecord(featuresType, &featureBytes),

		// Invoice state.
		tlv.MakePrimitiveRecord(invStateType, &state),
		tlv.MakePrimitiveRecord(amtPaidType, &amtPaid),

		tlv.MakePrimitiveRecord(hodlInvoiceType, &hodlInvoice),

		// Invoice AMP state.
		tlv.MakeDynamicRecord(
			invoiceAmpStateType, &i.AMPState,
			ampRecordSize(&i.AMPState),
			ampStateEncoder, ampStateDecoder,
		),
	)
	if err != nil {
		return err
	}

	var b bytes.Buffer
	if err = tlvStream.Encode(&b); err != nil {
		return err
	}

	err = binary.Write(w, byteOrder, uint64(b.Len()))
	if err != nil {
		return err
	}

	if _, err = w.Write(b.Bytes()); err != nil {
		return err
	}

	// Only if this is a _non_ AMP invoice do we serialize the HTLCs
	// in-line with the rest of the invoice.
	if i.IsAMP() {
		return nil
	}

	return serializeHtlcs(w, i.Htlcs)
}

// serializeHtlcs serializes a map containing circuit keys and invoice htlcs to
// a writer.
func serializeHtlcs(w io.Writer,
	htlcs map[models.CircuitKey]*invpkg.InvoiceHTLC) error {

	for key, htlc := range htlcs {
		// Encode the htlc in a tlv stream.
		chanID := key.ChanID.ToUint64()
		amt := uint64(htlc.Amt)
		mppTotalAmt := uint64(htlc.MppTotalAmt)
		acceptTime := putNanoTime(htlc.AcceptTime)
		resolveTime := putNanoTime(htlc.ResolveTime)
		state := uint8(htlc.State)

		var records []tlv.Record
		records = append(records,
			tlv.MakePrimitiveRecord(chanIDType, &chanID),
			tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID),
			tlv.MakePrimitiveRecord(amtType, &amt),
			tlv.MakePrimitiveRecord(
				acceptHeightType, &htlc.AcceptHeight,
			),
			tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime),
			tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime),
			tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry),
			tlv.MakePrimitiveRecord(htlcStateType, &state),
			tlv.MakePrimitiveRecord(mppTotalAmtType, &mppTotalAmt),
		)

		if htlc.AMP != nil {
			setIDRecord := tlv.MakeDynamicRecord(
				htlcAMPType, &htlc.AMP.Record,
				htlc.AMP.Record.PayloadSize,
				record.AMPEncoder, record.AMPDecoder,
			)
			records = append(records, setIDRecord)

			hash32 := [32]byte(htlc.AMP.Hash)
			hashRecord := tlv.MakePrimitiveRecord(
				htlcHashType, &hash32,
			)
			records = append(records, hashRecord)

			if htlc.AMP.Preimage != nil {
				preimage32 := [32]byte(*htlc.AMP.Preimage)
				preimageRecord := tlv.MakePrimitiveRecord(
					htlcPreimageType, &preimage32,
				)
				records = append(records, preimageRecord)
			}
		}

		// Convert the custom records to tlv.Record types that are ready
		// for serialization.
		customRecords := tlv.MapToRecords(htlc.CustomRecords)

		// Append the custom records. Their ids are in the experimental
		// range and sorted, so there is no need to sort again.
		records = append(records, customRecords...)

		tlvStream, err := tlv.NewStream(records...)
		if err != nil {
			return err
		}

		var b bytes.Buffer
		if err := tlvStream.Encode(&b); err != nil {
			return err
		}

		// Write the length of the tlv stream followed by the stream
		// bytes.
		err = binary.Write(w, byteOrder, uint64(b.Len()))
		if err != nil {
			return err
		}

		if _, err := w.Write(b.Bytes()); err != nil {
			return err
		}
	}

	return nil
}

// putNanoTime returns the unix nano time for the passed timestamp. A zero-value
// timestamp will be mapped to 0, since calling UnixNano in that case is
// undefined.
func putNanoTime(t time.Time) uint64 {
	if t.IsZero() {
		return 0
	}
	return uint64(t.UnixNano())
}

// getNanoTime returns a timestamp for the given number of nano seconds. If zero
// is provided, an zero-value time stamp is returned.
func getNanoTime(ns uint64) time.Time {
	if ns == 0 {
		return time.Time{}
	}
	return time.Unix(0, int64(ns))
}

// fetchFilteredAmpInvoices retrieves only a select set of AMP invoices
// identified by the setID value.
func fetchFilteredAmpInvoices(invoiceBucket kvdb.RBucket, invoiceNum []byte,
	setIDs ...*invpkg.SetID) (map[models.CircuitKey]*invpkg.InvoiceHTLC,
	error) {

	htlcs := make(map[models.CircuitKey]*invpkg.InvoiceHTLC)
	for _, setID := range setIDs {
		invoiceSetIDKey := makeInvoiceSetIDKey(invoiceNum, setID[:])

		htlcSetBytes := invoiceBucket.Get(invoiceSetIDKey[:])
		if htlcSetBytes == nil {
			// A set ID was passed in, but we don't have this
			// stored yet, meaning that the setID is being added
			// for the first time.
			return htlcs, invpkg.ErrInvoiceNotFound
		}

		htlcSetReader := bytes.NewReader(htlcSetBytes)
		htlcsBySetID, err := deserializeHtlcs(htlcSetReader)
		if err != nil {
			return nil, err
		}

		for key, htlc := range htlcsBySetID {
			htlcs[key] = htlc
		}
	}

	return htlcs, nil
}

// forEachAMPInvoice is a helper function that attempts to iterate over each of
// the HTLC sets (based on their set ID) for the given AMP invoice identified
// by its invoiceNum. The callback closure is called for each key within the
// prefix range.
func forEachAMPInvoice(invoiceBucket kvdb.RBucket, invoiceNum []byte,
	callback func(key, htlcSet []byte) error) error {

	invoiceCursor := invoiceBucket.ReadCursor()

	// Seek to the first key that includes the invoice data itself.
	invoiceCursor.Seek(invoiceNum)

	// Advance to the very first key _after_ the invoice data, as this is
	// where we'll encounter our first HTLC (if any are present).
	cursorKey, htlcSet := invoiceCursor.Next()

	// If at this point, the cursor key doesn't match the invoice num
	// prefix, then we know that this HTLC doesn't have any set ID HTLCs
	// associated with it.
	if !bytes.HasPrefix(cursorKey, invoiceNum) {
		return nil
	}

	// Otherwise continue to iterate until we no longer match the prefix,
	// executing the call back at each step.
	for ; cursorKey != nil && bytes.HasPrefix(cursorKey, invoiceNum); cursorKey, htlcSet = invoiceCursor.Next() {
		err := callback(cursorKey, htlcSet)
		if err != nil {
			return err
		}
	}

	return nil
}

// fetchAmpSubInvoices attempts to use the invoiceNum as a prefix  within the
// AMP bucket to find all the individual HTLCs (by setID) associated with a
// given invoice. If a list of set IDs are specified, then only HTLCs
// associated with that setID will be retrieved.
func fetchAmpSubInvoices(invoiceBucket kvdb.RBucket, invoiceNum []byte,
	setIDs ...*invpkg.SetID) (map[models.CircuitKey]*invpkg.InvoiceHTLC,
	error) {

	// If a set of setIDs was specified, then we can skip the cursor and
	// just read out exactly what we need.
	if len(setIDs) != 0 && setIDs[0] != nil {
		return fetchFilteredAmpInvoices(
			invoiceBucket, invoiceNum, setIDs...,
		)
	}

	// Otherwise, iterate over all the htlc sets that are prefixed beside
	// this invoice in the main invoice bucket.
	htlcs := make(map[models.CircuitKey]*invpkg.InvoiceHTLC)
	err := forEachAMPInvoice(invoiceBucket, invoiceNum,
		func(key, htlcSet []byte) error {
			htlcSetReader := bytes.NewReader(htlcSet)
			htlcsBySetID, err := deserializeHtlcs(htlcSetReader)
			if err != nil {
				return err
			}

			for key, htlc := range htlcsBySetID {
				htlcs[key] = htlc
			}

			return nil
		},
	)

	if err != nil {
		return nil, err
	}

	return htlcs, nil
}

// fetchInvoice attempts to read out the relevant state for the invoice as
// specified by the invoice number. If the setID fields are set, then only the
// HTLC information pertaining to those set IDs is returned.
func fetchInvoice(invoiceNum []byte, invoices kvdb.RBucket,
	setIDs []*invpkg.SetID, filterAMPState bool) (invpkg.Invoice, error) {

	invoiceBytes := invoices.Get(invoiceNum)
	if invoiceBytes == nil {
		return invpkg.Invoice{}, invpkg.ErrInvoiceNotFound
	}

	invoiceReader := bytes.NewReader(invoiceBytes)

	invoice, err := deserializeInvoice(invoiceReader)
	if err != nil {
		return invpkg.Invoice{}, err
	}

	// If this is an AMP invoice we'll also attempt to read out the set of
	// HTLCs that were paid to prior set IDs, if needed.
	if !invoice.IsAMP() {
		return invoice, nil
	}

	if shouldFetchAMPHTLCs(invoice, setIDs) {
		invoice.Htlcs, err = fetchAmpSubInvoices(
			invoices, invoiceNum, setIDs...,
		)
		// TODO(positiveblue): we should fail when we are not able to
		// fetch all the HTLCs for an AMP invoice. Multiple tests in
		// the invoice and channeldb package break if we return this
		// error. We need to update them when we migrate this logic to
		// the sql implementation.
		if err != nil {
			log.Errorf("unable to fetch amp htlcs for inv "+
				"%v and setIDs %v: %w", invoiceNum, setIDs, err)
		}

		if filterAMPState {
			filterInvoiceAMPState(&invoice, setIDs...)
		}
	}

	return invoice, nil
}

// shouldFetchAMPHTLCs returns true if we need to fetch the set of HTLCs that
// were paid to the relevant set IDs.
func shouldFetchAMPHTLCs(invoice invpkg.Invoice, setIDs []*invpkg.SetID) bool {
	// For AMP invoice that already have HTLCs populated (created before
	// recurring invoices), then we don't need to read from the prefix
	// keyed section of the bucket.
	if len(invoice.Htlcs) != 0 {
		return false
	}

	// If the "zero" setID was specified, then this means that no HTLC data
	// should be returned alongside of it.
	if len(setIDs) != 0 && setIDs[0] != nil &&
		*setIDs[0] == invpkg.BlankPayAddr {

		return false
	}

	return true
}

// fetchInvoiceStateAMP retrieves the state of all the relevant sub-invoice for
// an AMP invoice. This methods only decode the relevant state vs the entire
// invoice.
func fetchInvoiceStateAMP(invoiceNum []byte,
	invoices kvdb.RBucket) (invpkg.AMPInvoiceState, error) {

	// Fetch the raw invoice bytes.
	invoiceBytes := invoices.Get(invoiceNum)
	if invoiceBytes == nil {
		return nil, invpkg.ErrInvoiceNotFound
	}

	r := bytes.NewReader(invoiceBytes)

	var bodyLen int64
	err := binary.Read(r, byteOrder, &bodyLen)
	if err != nil {
		return nil, err
	}

	// Next, we'll make a new TLV stream that only attempts to decode the
	// bytes we actually need.
	ampState := make(invpkg.AMPInvoiceState)
	tlvStream, err := tlv.NewStream(
		// Invoice AMP state.
		tlv.MakeDynamicRecord(
			invoiceAmpStateType, &ampState, nil,
			ampStateEncoder, ampStateDecoder,
		),
	)
	if err != nil {
		return nil, err
	}

	invoiceReader := io.LimitReader(r, bodyLen)
	if err = tlvStream.Decode(invoiceReader); err != nil {
		return nil, err
	}

	return ampState, nil
}

func deserializeInvoice(r io.Reader) (invpkg.Invoice, error) {
	var (
		preimageBytes [32]byte
		value         uint64
		cltvDelta     uint32
		expiry        uint64
		amtPaid       uint64
		state         uint8
		hodlInvoice   uint8

		creationDateBytes []byte
		settleDateBytes   []byte
		featureBytes      []byte
	)

	var i invpkg.Invoice
	i.AMPState = make(invpkg.AMPInvoiceState)
	tlvStream, err := tlv.NewStream(
		// Memo and payreq.
		tlv.MakePrimitiveRecord(memoType, &i.Memo),
		tlv.MakePrimitiveRecord(payReqType, &i.PaymentRequest),

		// Add/settle metadata.
		tlv.MakePrimitiveRecord(createTimeType, &creationDateBytes),
		tlv.MakePrimitiveRecord(settleTimeType, &settleDateBytes),
		tlv.MakePrimitiveRecord(addIndexType, &i.AddIndex),
		tlv.MakePrimitiveRecord(settleIndexType, &i.SettleIndex),

		// Terms.
		tlv.MakePrimitiveRecord(preimageType, &preimageBytes),
		tlv.MakePrimitiveRecord(valueType, &value),
		tlv.MakePrimitiveRecord(cltvDeltaType, &cltvDelta),
		tlv.MakePrimitiveRecord(expiryType, &expiry),
		tlv.MakePrimitiveRecord(paymentAddrType, &i.Terms.PaymentAddr),
		tlv.MakePrimitiveRecord(featuresType, &featureBytes),

		// Invoice state.
		tlv.MakePrimitiveRecord(invStateType, &state),
		tlv.MakePrimitiveRecord(amtPaidType, &amtPaid),

		tlv.MakePrimitiveRecord(hodlInvoiceType, &hodlInvoice),

		// Invoice AMP state.
		tlv.MakeDynamicRecord(
			invoiceAmpStateType, &i.AMPState, nil,
			ampStateEncoder, ampStateDecoder,
		),
	)
	if err != nil {
		return i, err
	}

	var bodyLen int64
	err = binary.Read(r, byteOrder, &bodyLen)
	if err != nil {
		return i, err
	}

	lr := io.LimitReader(r, bodyLen)
	if err = tlvStream.Decode(lr); err != nil {
		return i, err
	}

	preimage := lntypes.Preimage(preimageBytes)
	if preimage != invpkg.UnknownPreimage {
		i.Terms.PaymentPreimage = &preimage
	}

	i.Terms.Value = lnwire.MilliSatoshi(value)
	i.Terms.FinalCltvDelta = int32(cltvDelta)
	i.Terms.Expiry = time.Duration(expiry)
	i.AmtPaid = lnwire.MilliSatoshi(amtPaid)
	i.State = invpkg.ContractState(state)

	if hodlInvoice != 0 {
		i.HodlInvoice = true
	}

	err = i.CreationDate.UnmarshalBinary(creationDateBytes)
	if err != nil {
		return i, err
	}

	err = i.SettleDate.UnmarshalBinary(settleDateBytes)
	if err != nil {
		return i, err
	}

	rawFeatures := lnwire.NewRawFeatureVector()
	err = rawFeatures.DecodeBase256(
		bytes.NewReader(featureBytes), len(featureBytes),
	)
	if err != nil {
		return i, err
	}

	i.Terms.Features = lnwire.NewFeatureVector(
		rawFeatures, lnwire.Features,
	)

	i.Htlcs, err = deserializeHtlcs(r)
	return i, err
}

func encodeCircuitKeys(w io.Writer, val interface{}, buf *[8]byte) error {
	if v, ok := val.(*map[models.CircuitKey]struct{}); ok {
		// We encode the set of circuit keys as a varint length prefix.
		// followed by a series of fixed sized uint8 integers.
		numKeys := uint64(len(*v))

		if err := tlv.WriteVarInt(w, numKeys, buf); err != nil {
			return err
		}

		for key := range *v {
			scidInt := key.ChanID.ToUint64()

			if err := tlv.EUint64(w, &scidInt, buf); err != nil {
				return err
			}
			if err := tlv.EUint64(w, &key.HtlcID, buf); err != nil {
				return err
			}
		}

		return nil
	}

	return tlv.NewTypeForEncodingErr(val, "*map[CircuitKey]struct{}")
}

func decodeCircuitKeys(r io.Reader, val interface{}, buf *[8]byte,
	l uint64) error {

	if v, ok := val.(*map[models.CircuitKey]struct{}); ok {
		// First, we'll read out the varint that encodes the number of
		// circuit keys encoded.
		numKeys, err := tlv.ReadVarInt(r, buf)
		if err != nil {
			return err
		}

		// Now that we know how many keys to expect, iterate reading
		// each one until we're done.
		for i := uint64(0); i < numKeys; i++ {
			var (
				key  models.CircuitKey
				scid uint64
			)

			if err := tlv.DUint64(r, &scid, buf, 8); err != nil {
				return err
			}

			key.ChanID = lnwire.NewShortChanIDFromInt(scid)

			err := tlv.DUint64(r, &key.HtlcID, buf, 8)
			if err != nil {
				return err
			}

			(*v)[key] = struct{}{}
		}

		return nil
	}

	return tlv.NewTypeForDecodingErr(val, "*map[CircuitKey]struct{}", l, l)
}

// ampStateEncoder is a custom TLV encoder for the AMPInvoiceState record.
func ampStateEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
	if v, ok := val.(*invpkg.AMPInvoiceState); ok {
		// We'll encode the AMP state as a series of KV pairs on the
		// wire with a length prefix.
		numRecords := uint64(len(*v))

		// First, we'll write out the number of records as a var int.
		if err := tlv.WriteVarInt(w, numRecords, buf); err != nil {
			return err
		}

		// With that written out, we'll now encode the entries
		// themselves as a sub-TLV record, which includes its _own_
		// inner length prefix.
		for setID, ampState := range *v {
			setID := [32]byte(setID)
			ampState := ampState

			htlcState := uint8(ampState.State)
			settleDate := ampState.SettleDate
			settleDateBytes, err := settleDate.MarshalBinary()
			if err != nil {
				return err
			}

			amtPaid := uint64(ampState.AmtPaid)

			var ampStateTlvBytes bytes.Buffer
			tlvStream, err := tlv.NewStream(
				tlv.MakePrimitiveRecord(
					ampStateSetIDType, &setID,
				),
				tlv.MakePrimitiveRecord(
					ampStateHtlcStateType, &htlcState,
				),
				tlv.MakePrimitiveRecord(
					ampStateSettleIndexType,
					&ampState.SettleIndex,
				),
				tlv.MakePrimitiveRecord(
					ampStateSettleDateType,
					&settleDateBytes,
				),
				tlv.MakeDynamicRecord(
					ampStateCircuitKeysType,
					&ampState.InvoiceKeys,
					func() uint64 {
						// The record takes 8 bytes to
						// encode the set of circuits,
						// 8 bytes for the scid for the
						// key, and 8 bytes for the HTLC
						// index.
						keys := ampState.InvoiceKeys
						numKeys := uint64(len(keys))
						size := tlv.VarIntSize(numKeys)
						dataSize := (numKeys * 16)

						return size + dataSize
					},
					encodeCircuitKeys, decodeCircuitKeys,
				),
				tlv.MakePrimitiveRecord(
					ampStateAmtPaidType, &amtPaid,
				),
			)
			if err != nil {
				return err
			}

			err = tlvStream.Encode(&ampStateTlvBytes)
			if err != nil {
				return err
			}

			// We encode the record with a varint length followed by
			// the _raw_ TLV bytes.
			tlvLen := uint64(len(ampStateTlvBytes.Bytes()))
			if err := tlv.WriteVarInt(w, tlvLen, buf); err != nil {
				return err
			}

			_, err = w.Write(ampStateTlvBytes.Bytes())
			if err != nil {
				return err
			}
		}

		return nil
	}

	return tlv.NewTypeForEncodingErr(val, "channeldb.AMPInvoiceState")
}

// ampStateDecoder is a custom TLV decoder for the AMPInvoiceState record.
func ampStateDecoder(r io.Reader, val interface{}, buf *[8]byte,
	l uint64) error {

	if v, ok := val.(*invpkg.AMPInvoiceState); ok {
		// First, we'll decode the varint that encodes how many set IDs
		// are encoded within the greater map.
		numRecords, err := tlv.ReadVarInt(r, buf)
		if err != nil {
			return err
		}

		// Now that we know how many records we'll need to read, we can
		// iterate and read them all out in series.
		for i := uint64(0); i < numRecords; i++ {
			// Read out the varint that encodes the size of this
			// inner TLV record.
			stateRecordSize, err := tlv.ReadVarInt(r, buf)
			if err != nil {
				return err
			}

			// Using this information, we'll create a new limited
			// reader that'll return an EOF once the end has been
			// reached so the stream stops consuming bytes.
			innerTlvReader := io.LimitedReader{
				R: r,
				N: int64(stateRecordSize),
			}

			var (
				setID           [32]byte
				htlcState       uint8
				settleIndex     uint64
				settleDateBytes []byte
				invoiceKeys     = make(
					map[models.CircuitKey]struct{},
				)
				amtPaid uint64
			)
			tlvStream, err := tlv.NewStream(
				tlv.MakePrimitiveRecord(
					ampStateSetIDType, &setID,
				),
				tlv.MakePrimitiveRecord(
					ampStateHtlcStateType, &htlcState,
				),
				tlv.MakePrimitiveRecord(
					ampStateSettleIndexType, &settleIndex,
				),
				tlv.MakePrimitiveRecord(
					ampStateSettleDateType,
					&settleDateBytes,
				),
				tlv.MakeDynamicRecord(
					ampStateCircuitKeysType,
					&invoiceKeys, nil,
					encodeCircuitKeys, decodeCircuitKeys,
				),
				tlv.MakePrimitiveRecord(
					ampStateAmtPaidType, &amtPaid,
				),
			)
			if err != nil {
				return err
			}

			err = tlvStream.Decode(&innerTlvReader)
			if err != nil {
				return err
			}

			var settleDate time.Time
			err = settleDate.UnmarshalBinary(settleDateBytes)
			if err != nil {
				return err
			}

			(*v)[setID] = invpkg.InvoiceStateAMP{
				State:       invpkg.HtlcState(htlcState),
				SettleIndex: settleIndex,
				SettleDate:  settleDate,
				InvoiceKeys: invoiceKeys,
				AmtPaid:     lnwire.MilliSatoshi(amtPaid),
			}
		}

		return nil
	}

	return tlv.NewTypeForDecodingErr(
		val, "channeldb.AMPInvoiceState", l, l,
	)
}

// deserializeHtlcs reads a list of invoice htlcs from a reader and returns it
// as a map.
func deserializeHtlcs(r io.Reader) (map[models.CircuitKey]*invpkg.InvoiceHTLC,
	error) {

	htlcs := make(map[models.CircuitKey]*invpkg.InvoiceHTLC)
	for {
		// Read the length of the tlv stream for this htlc.
		var streamLen int64
		if err := binary.Read(r, byteOrder, &streamLen); err != nil {
			if err == io.EOF {
				break
			}

			return nil, err
		}

		// Limit the reader so that it stops at the end of this htlc's
		// stream.
		htlcReader := io.LimitReader(r, streamLen)

		// Decode the contents into the htlc fields.
		var (
			htlc                    invpkg.InvoiceHTLC
			key                     models.CircuitKey
			chanID                  uint64
			state                   uint8
			acceptTime, resolveTime uint64
			amt, mppTotalAmt        uint64
			amp                     = &record.AMP{}
			hash32                  = &[32]byte{}
			preimage32              = &[32]byte{}
		)
		tlvStream, err := tlv.NewStream(
			tlv.MakePrimitiveRecord(chanIDType, &chanID),
			tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID),
			tlv.MakePrimitiveRecord(amtType, &amt),
			tlv.MakePrimitiveRecord(
				acceptHeightType, &htlc.AcceptHeight,
			),
			tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime),
			tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime),
			tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry),
			tlv.MakePrimitiveRecord(htlcStateType, &state),
			tlv.MakePrimitiveRecord(mppTotalAmtType, &mppTotalAmt),
			tlv.MakeDynamicRecord(
				htlcAMPType, amp, amp.PayloadSize,
				record.AMPEncoder, record.AMPDecoder,
			),
			tlv.MakePrimitiveRecord(htlcHashType, hash32),
			tlv.MakePrimitiveRecord(htlcPreimageType, preimage32),
		)
		if err != nil {
			return nil, err
		}

		parsedTypes, err := tlvStream.DecodeWithParsedTypes(htlcReader)
		if err != nil {
			return nil, err
		}

		if _, ok := parsedTypes[htlcAMPType]; !ok {
			amp = nil
		}

		var preimage *lntypes.Preimage
		if _, ok := parsedTypes[htlcPreimageType]; ok {
			pimg := lntypes.Preimage(*preimage32)
			preimage = &pimg
		}

		var hash *lntypes.Hash
		if _, ok := parsedTypes[htlcHashType]; ok {
			h := lntypes.Hash(*hash32)
			hash = &h
		}

		key.ChanID = lnwire.NewShortChanIDFromInt(chanID)
		htlc.AcceptTime = getNanoTime(acceptTime)
		htlc.ResolveTime = getNanoTime(resolveTime)
		htlc.State = invpkg.HtlcState(state)
		htlc.Amt = lnwire.MilliSatoshi(amt)
		htlc.MppTotalAmt = lnwire.MilliSatoshi(mppTotalAmt)
		if amp != nil && hash != nil {
			htlc.AMP = &invpkg.InvoiceHtlcAMPData{
				Record:   *amp,
				Hash:     *hash,
				Preimage: preimage,
			}
		}

		// Reconstruct the custom records fields from the parsed types
		// map return from the tlv parser.
		htlc.CustomRecords = hop.NewCustomRecords(parsedTypes)

		htlcs[key] = &htlc
	}

	return htlcs, nil
}

// invoiceSetIDKeyLen is the length of the key that's used to store the
// individual HTLCs prefixed by their ID along side the main invoice within the
// invoiceBytes. We use 4 bytes for the invoice number, and 32 bytes for the
// set ID.
const invoiceSetIDKeyLen = 4 + 32

// makeInvoiceSetIDKey returns the prefix key, based on the set ID and invoice
// number where the HTLCs for this setID will be stored udner.
func makeInvoiceSetIDKey(invoiceNum, setID []byte) [invoiceSetIDKeyLen]byte {
	// Construct the prefix key we need to obtain the invoice information:
	// invoiceNum || setID.
	var invoiceSetIDKey [invoiceSetIDKeyLen]byte
	copy(invoiceSetIDKey[:], invoiceNum)
	copy(invoiceSetIDKey[len(invoiceNum):], setID)

	return invoiceSetIDKey
}

// delAMPInvoices attempts to delete all the "sub" invoices associated with a
// greater AMP invoices. We do this by deleting the set of keys that share the
// invoice number as a prefix.
func delAMPInvoices(invoiceNum []byte, invoiceBucket kvdb.RwBucket) error {
	// Since it isn't safe to delete using an active cursor, we'll use the
	// cursor simply to collect the set of keys we need to delete, _then_
	// delete them in another pass.
	var keysToDel [][]byte
	err := forEachAMPInvoice(
		invoiceBucket, invoiceNum,
		func(cursorKey, v []byte) error {
			keysToDel = append(keysToDel, cursorKey)
			return nil
		},
	)
	if err != nil {
		return err
	}

	// In this next phase, we'll then delete all the relevant invoices.
	for _, keyToDel := range keysToDel {
		if err := invoiceBucket.Delete(keyToDel); err != nil {
			return err
		}
	}

	return nil
}

// delAMPSettleIndex removes all the entries in the settle index associated
// with a given AMP invoice.
func delAMPSettleIndex(invoiceNum []byte, invoices,
	settleIndex kvdb.RwBucket) error {

	// First, we need to grab the AMP invoice state to see if there's
	// anything that we even need to delete.
	ampState, err := fetchInvoiceStateAMP(invoiceNum, invoices)
	if err != nil {
		return err
	}

	// If there's no AMP state at all (non-AMP invoice), then we can return
	// early.
	if len(ampState) == 0 {
		return nil
	}

	// Otherwise, we'll need to iterate and delete each settle index within
	// the set of returned entries.
	var settleIndexKey [8]byte
	for _, subState := range ampState {
		byteOrder.PutUint64(
			settleIndexKey[:], subState.SettleIndex,
		)

		if err := settleIndex.Delete(settleIndexKey[:]); err != nil {
			return err
		}
	}

	return nil
}

// DeleteCanceledInvoices deletes all canceled invoices from the database.
func (d *DB) DeleteCanceledInvoices(_ context.Context) error {
	return kvdb.Update(d, func(tx kvdb.RwTx) error {
		invoices := tx.ReadWriteBucket(invoiceBucket)
		if invoices == nil {
			return nil
		}

		invoiceIndex := invoices.NestedReadWriteBucket(
			invoiceIndexBucket,
		)
		if invoiceIndex == nil {
			return nil
		}

		invoiceAddIndex := invoices.NestedReadWriteBucket(
			addIndexBucket,
		)
		if invoiceAddIndex == nil {
			return nil
		}

		payAddrIndex := tx.ReadWriteBucket(payAddrIndexBucket)

		return invoiceIndex.ForEach(func(k, v []byte) error {
			// Skip the special numInvoicesKey as that does not
			// point to a valid invoice.
			if bytes.Equal(k, numInvoicesKey) {
				return nil
			}

			// Skip sub-buckets.
			if v == nil {
				return nil
			}

			invoice, err := fetchInvoice(v, invoices, nil, false)
			if err != nil {
				return err
			}

			if invoice.State != invpkg.ContractCanceled {
				return nil
			}

			// Delete the payment hash from the invoice index.
			err = invoiceIndex.Delete(k)
			if err != nil {
				return err
			}

			// Delete payment address index reference if there's a
			// valid payment address.
			if invoice.Terms.PaymentAddr != invpkg.BlankPayAddr {
				// To ensure consistency check that the already
				// fetched invoice key matches the one in the
				// payment address index.
				key := payAddrIndex.Get(
					invoice.Terms.PaymentAddr[:],
				)
				if bytes.Equal(key, k) {
					// Delete from the payment address
					// index.
					if err := payAddrIndex.Delete(
						invoice.Terms.PaymentAddr[:],
					); err != nil {
						return err
					}
				}
			}

			// Remove from the add index.
			var addIndexKey [8]byte
			byteOrder.PutUint64(addIndexKey[:], invoice.AddIndex)
			err = invoiceAddIndex.Delete(addIndexKey[:])
			if err != nil {
				return err
			}

			// Note that we don't need to delete the invoice from
			// the settle index as it is not added until the
			// invoice is settled.

			// Now remove all sub invoices.
			err = delAMPInvoices(k, invoices)
			if err != nil {
				return err
			}

			// Finally remove the serialized invoice from the
			// invoice bucket.
			return invoices.Delete(k)
		})
	}, func() {})
}

// DeleteInvoice attempts to delete the passed invoices from the database in
// one transaction. The passed delete references hold all keys required to
// delete the invoices without also needing to deserialize them.
func (d *DB) DeleteInvoice(_ context.Context,
	invoicesToDelete []invpkg.InvoiceDeleteRef) error {

	err := kvdb.Update(d, func(tx kvdb.RwTx) error {
		invoices := tx.ReadWriteBucket(invoiceBucket)
		if invoices == nil {
			return invpkg.ErrNoInvoicesCreated
		}

		invoiceIndex := invoices.NestedReadWriteBucket(
			invoiceIndexBucket,
		)
		if invoiceIndex == nil {
			return invpkg.ErrNoInvoicesCreated
		}

		invoiceAddIndex := invoices.NestedReadWriteBucket(
			addIndexBucket,
		)
		if invoiceAddIndex == nil {
			return invpkg.ErrNoInvoicesCreated
		}

		// settleIndex can be nil, as the bucket is created lazily
		// when the first invoice is settled.
		settleIndex := invoices.NestedReadWriteBucket(settleIndexBucket)

		payAddrIndex := tx.ReadWriteBucket(payAddrIndexBucket)

		for _, ref := range invoicesToDelete {
			// Fetch the invoice key for using it to check for
			// consistency and also to delete from the invoice
			// index.
			invoiceKey := invoiceIndex.Get(ref.PayHash[:])
			if invoiceKey == nil {
				return invpkg.ErrInvoiceNotFound
			}

			err := invoiceIndex.Delete(ref.PayHash[:])
			if err != nil {
				return err
			}

			// Delete payment address index reference if there's a
			// valid payment address passed.
			if ref.PayAddr != nil {
				// To ensure consistency check that the already
				// fetched invoice key matches the one in the
				// payment address index.
				key := payAddrIndex.Get(ref.PayAddr[:])
				if bytes.Equal(key, invoiceKey) {
					// Delete from the payment address
					// index. Note that since the payment
					// address index has been introduced
					// with an empty migration it may be
					// possible that the index doesn't have
					// an entry for this invoice.
					// ref: https://github.com/lightningnetwork/lnd/pull/4285/commits/cbf71b5452fa1d3036a43309e490787c5f7f08dc#r426368127
					if err := payAddrIndex.Delete(
						ref.PayAddr[:],
					); err != nil {
						return err
					}
				}
			}

			var addIndexKey [8]byte
			byteOrder.PutUint64(addIndexKey[:], ref.AddIndex)

			// To ensure consistency check that the key stored in
			// the add index also matches the previously fetched
			// invoice key.
			key := invoiceAddIndex.Get(addIndexKey[:])
			if !bytes.Equal(key, invoiceKey) {
				return fmt.Errorf("unknown invoice " +
					"in add index")
			}

			// Remove from the add index.
			err = invoiceAddIndex.Delete(addIndexKey[:])
			if err != nil {
				return err
			}

			// Remove from the settle index if available and
			// if the invoice is settled.
			if settleIndex != nil && ref.SettleIndex > 0 {
				var settleIndexKey [8]byte
				byteOrder.PutUint64(
					settleIndexKey[:], ref.SettleIndex,
				)

				// To ensure consistency check that the already
				// fetched invoice key matches the one in the
				// settle index
				key := settleIndex.Get(settleIndexKey[:])
				if !bytes.Equal(key, invoiceKey) {
					return fmt.Errorf("unknown invoice " +
						"in settle index")
				}

				err = settleIndex.Delete(settleIndexKey[:])
				if err != nil {
					return err
				}
			}

			// In addition to deleting the main invoice state, if
			// this is an AMP invoice, then we'll also need to
			// delete the set HTLC set stored as a key prefix. For
			// non-AMP invoices, this'll be a noop.
			err = delAMPSettleIndex(
				invoiceKey, invoices, settleIndex,
			)
			if err != nil {
				return err
			}
			err = delAMPInvoices(invoiceKey, invoices)
			if err != nil {
				return err
			}

			// Finally remove the serialized invoice from the
			// invoice bucket.
			err = invoices.Delete(invoiceKey)
			if err != nil {
				return err
			}
		}

		return nil
	}, func() {})

	return err
}
