package migration24

import (
	"bytes"
	"encoding/binary"
	"io"

	mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
	"github.com/lightningnetwork/lnd/kvdb"
)

var (
	// closedChannelBucket stores summarization information concerning
	// previously open, but now closed channels.
	closedChannelBucket = []byte("closed-chan-bucket")

	// fwdPackagesKey is the root-level bucket that all forwarding packages
	// are written. This bucket is further subdivided based on the short
	// channel ID of each channel.
	fwdPackagesKey = []byte("fwd-packages")
)

// MigrateFwdPkgCleanup deletes all the forwarding packages of closed channels.
// It determines the closed channels by iterating closedChannelBucket. The
// ShortChanID found in the ChannelCloseSummary is then used as a key to query
// the forwarding packages bucket. If a match is found, it will be deleted.
func MigrateFwdPkgCleanup(tx kvdb.RwTx) error {
	log.Infof("Deleting forwarding packages for closed channels")

	// Find all closed channel summaries, which are stored in the
	// closeBucket.
	closeBucket := tx.ReadBucket(closedChannelBucket)
	if closeBucket == nil {
		return nil
	}

	var chanSummaries []*mig.ChannelCloseSummary

	// appendSummary is a function closure to help put deserialized close
	// summeries into chanSummaries.
	appendSummary := func(_ []byte, summaryBytes []byte) error {
		summaryReader := bytes.NewReader(summaryBytes)
		chanSummary, err := deserializeCloseChannelSummary(
			summaryReader,
		)
		if err != nil {
			return err
		}

		// Skip pending channels
		if chanSummary.IsPending {
			return nil
		}

		chanSummaries = append(chanSummaries, chanSummary)
		return nil
	}

	if err := closeBucket.ForEach(appendSummary); err != nil {
		return err
	}

	// Now we will load the forwarding packages bucket, delete all the
	// nested buckets whose source matches the ShortChanID found in the
	// closed channel summeraries.
	fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
	if fwdPkgBkt == nil {
		return nil
	}

	// Iterate over all close channels and remove their forwarding packages.
	for _, summery := range chanSummaries {
		sourceBytes := MakeLogKey(summery.ShortChanID.ToUint64())

		// First, we will try to find the corresponding bucket. If there
		// is not a nested bucket matching the ShortChanID, we will skip
		// it.
		if fwdPkgBkt.NestedReadBucket(sourceBytes[:]) == nil {
			continue
		}

		// Otherwise, wipe all the forwarding packages.
		if err := fwdPkgBkt.DeleteNestedBucket(
			sourceBytes[:],
		); err != nil {
			return err
		}
	}

	log.Infof("Deletion of forwarding packages of closed channels " +
		"complete! DB compaction is recommended to free up the" +
		"disk space.")
	return nil
}

// deserializeCloseChannelSummary will decode a CloseChannelSummary with no
// optional fields.
func deserializeCloseChannelSummary(
	r io.Reader) (*mig.ChannelCloseSummary, error) {

	c := &mig.ChannelCloseSummary{}
	err := mig.ReadElements(
		r,
		&c.ChanPoint, &c.ShortChanID, &c.ChainHash, &c.ClosingTXID,
		&c.CloseHeight, &c.RemotePub, &c.Capacity, &c.SettledBalance,
		&c.TimeLockedBalance, &c.CloseType, &c.IsPending,
	)
	if err != nil {
		return nil, err
	}
	return c, nil
}

// makeLogKey converts a uint64 into an 8 byte array.
func MakeLogKey(updateNum uint64) [8]byte {
	var key [8]byte
	binary.BigEndian.PutUint64(key[:], updateNum)
	return key
}
