package loopdb

import (
	"errors"
	"fmt"

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

// migrateUpdates migrates the swap updates to add an additional level of
// nesting, allowing for optional keys to be added.
func migrateUpdates(tx *bbolt.Tx, chainParams *chaincfg.Params) error {
	for _, key := range [][]byte{loopInBucketKey, loopOutBucketKey} {
		rootBucket := tx.Bucket(key)
		if rootBucket == nil {
			return fmt.Errorf("bucket %v does not exist", key)
		}

		err := migrateSwapTypeUpdates(rootBucket)
		if err != nil {
			return err
		}
	}

	return nil
}

// migrateSwapTypeUpdates migrates updates for swaps in the specified bucket.
func migrateSwapTypeUpdates(rootBucket *bbolt.Bucket) error {
	var swaps [][]byte

	// Do not modify inside the for each.
	err := rootBucket.ForEach(func(swapHash, v []byte) error {
		// Only go into things that we know are sub-bucket
		// keys.
		if rootBucket.Bucket(swapHash) != nil {
			swaps = append(swaps, swapHash)
		}

		return nil
	})
	if err != nil {
		return err
	}

	// With the swaps listed, migrate them one by one.
	for _, swapHash := range swaps {
		swapBucket := rootBucket.Bucket(swapHash)
		if swapBucket == nil {
			return fmt.Errorf("swap bucket %x not found",
				swapHash)
		}

		err := migrateSwapUpdates(swapBucket)
		if err != nil {
			return err
		}
	}

	return nil
}

// migrateSwapUpdates migrates updates for the swap stored in the specified
// bucket.
func migrateSwapUpdates(swapBucket *bbolt.Bucket) error {
	// With the main swap bucket obtained, we'll grab the
	// raw swap contract bytes.
	updatesBucket := swapBucket.Bucket(updatesBucketKey)
	if updatesBucket == nil {
		return errors.New("updates bucket not found")
	}

	type state struct {
		id, state []byte
	}

	var existingStates []state

	// Do not modify inside the for each.
	err := updatesBucket.ForEach(func(k, v []byte) error {
		existingStates = append(existingStates, state{id: k, state: v})
		return nil
	})
	if err != nil {
		return err
	}

	for _, existingState := range existingStates {
		// Delete the existing state key.
		err := updatesBucket.Delete(existingState.id)
		if err != nil {
			return err
		}

		// Re-create as a bucket.
		updateBucket, err := updatesBucket.CreateBucket(
			existingState.id,
		)
		if err != nil {
			return err
		}

		// Write back the basic state as a sub-key.
		err = updateBucket.Put(basicStateKey, existingState.state)
		if err != nil {
			return err
		}
	}

	return nil
}
