package loopdb

import (
	"bytes"
	"errors"
	"fmt"

	"github.com/btcsuite/btcd/chaincfg"
	"go.etcd.io/bbolt"
)

// migrateLastHop migrates the database to v03, replacing the never used loop in
// channel by a last hop pubkey.
func migrateLastHop(tx *bbolt.Tx, chainParams *chaincfg.Params) error {
	rootBucket := tx.Bucket(loopInBucketKey)
	if rootBucket == nil {
		return errors.New("bucket does not exist")
	}

	return rootBucket.ForEach(func(swapHash, v []byte) error {
		// Only go into things that we know are sub-bucket
		// keys.
		if v != nil {
			return nil
		}

		// From the root bucket, we'll grab the next swap
		// bucket for this swap from its swaphash.
		swapBucket := rootBucket.Bucket(swapHash)
		if swapBucket == nil {
			return fmt.Errorf("swap bucket %x not found",
				swapHash)
		}

		// With the main swap bucket obtained, we'll grab the
		// raw swap contract bytes.
		contractBytes := swapBucket.Get(contractKey)
		if contractBytes == nil {
			return errors.New("contract not found")
		}

		const (
			// deprecatedLoopInChannelStart is the starting offset
			// of the old loop in channel field: 8 (initiation time)
			// + 32 (preimage) + 8 (amount requested) + 33 + 33
			// (sender and receiver keys) + 4 (cltv expiry) + 8 + 8
			// (max miner and swap fee) + 4 (initiation height) + 4
			// (conf target) = 142.
			deprecatedLoopInChannelStart = 142

			// expectedTotalLength is the expect total length of the
			// serialized contract. It adds 8 (old loop in channel)
			// + 1 (external htlc) = 9 bytes to
			// deprecatedLoopInChannelStart.
			expectedTotalLength = deprecatedLoopInChannelStart + 9
		)

		// Sanity check to see if the constants above match the contract
		// bytes.
		if len(contractBytes) != expectedTotalLength {
			return errors.New("invalid serialized contract length")
		}

		// Copy the unchanged fields into the buffer.
		b := &bytes.Buffer{}
		_, err := b.Write(contractBytes[:deprecatedLoopInChannelStart])
		if err != nil {
			return err
		}

		// We now set the new last hop field to all zeroes to indicate
		// that there is no restriction.
		var noLastHop [33]byte
		if _, err := b.Write(noLastHop[:]); err != nil {
			return err
		}

		// Append the remaining field ExternalHtlc.
		_, err = b.Write(contractBytes[deprecatedLoopInChannelStart+8:])
		if err != nil {
			return err
		}

		return swapBucket.Put(contractKey, b.Bytes())
	})
}
