package chanbackup

import (
	"net"

	"github.com/btcsuite/btcd/btcec"
	"github.com/davecgh/go-spew/spew"
	"github.com/lightningnetwork/lnd/channeldb"
	"github.com/lightningnetwork/lnd/keychain"
)

// ChannelRestorer is an interface that allows the Recover method to map the
// set of single channel backups into a set of "channel shells" and store these
// persistently on disk. The channel shell should contain all the information
// needed to execute the data loss recovery protocol once the channel peer is
// connected to.
type ChannelRestorer interface {
	// RestoreChansFromSingles attempts to map the set of single channel
	// backups to channel shells that will be stored persistently. Once
	// these shells have been stored on disk, we'll be able to connect to
	// the channel peer an execute the data loss recovery protocol.
	RestoreChansFromSingles(...Single) error
}

// PeerConnector is an interface that allows the Recover method to connect to
// the target node given the set of possible addresses.
type PeerConnector interface {
	// ConnectPeer attempts to connect to the target node at the set of
	// available addresses. Once this method returns with a non-nil error,
	// the connector should attempt to persistently connect to the target
	// peer in the background as a persistent attempt.
	ConnectPeer(node *btcec.PublicKey, addrs []net.Addr) error
}

// Recover attempts to recover the static channel state from a set of static
// channel backups. If successfully, the database will be populated with a
// series of "shell" channels. These "shell" channels cannot be used to operate
// the channel as normal, but instead are meant to be used to enter the data
// loss recovery phase, and recover the settled funds within
// the channel. In addition a LinkNode will be created for each new peer as
// well, in order to expose the addressing information required to locate to
// and connect to each peer in order to initiate the recovery protocol.
func Recover(backups []Single, restorer ChannelRestorer,
	peerConnector PeerConnector) error {

	for i, backup := range backups {
		log.Infof("Restoring ChannelPoint(%v) to disk: ",
			backup.FundingOutpoint)

		err := restorer.RestoreChansFromSingles(backup)

		// If a channel is already present in the channel DB, we can
		// just continue. No reason to fail a whole set of multi backups
		// for example. This allows resume of a restore in case another
		// error happens.
		if err == channeldb.ErrChanAlreadyExists {
			continue
		}
		if err != nil {
			return err
		}

		log.Infof("Attempting to connect to node=%x (addrs=%v) to "+
			"restore ChannelPoint(%v)",
			backup.RemoteNodePub.SerializeCompressed(),
			newLogClosure(func() string {
				return spew.Sdump(backups[i].Addresses)
			}), backup.FundingOutpoint)

		err = peerConnector.ConnectPeer(
			backup.RemoteNodePub, backup.Addresses,
		)
		if err != nil {
			return err
		}

		// TODO(roasbeef): to handle case where node has changed addrs,
		// need to subscribe to new updates for target node pub to
		// attempt to connect to other addrs
		//
		//  * just to to fresh w/ call to node addrs and de-dup?
	}

	return nil
}

// TODO(roasbeef): more specific keychain interface?

// UnpackAndRecoverSingles is a one-shot method, that given a set of packed
// single channel backups, will restore the channel state to a channel shell,
// and also reach out to connect to any of the known node addresses for that
// channel. It is assumes that after this method exists, if a connection we
// able to be established, then then PeerConnector will continue to attempt to
// re-establish a persistent connection in the background.
func UnpackAndRecoverSingles(singles PackedSingles,
	keyChain keychain.KeyRing, restorer ChannelRestorer,
	peerConnector PeerConnector) error {

	chanBackups, err := singles.Unpack(keyChain)
	if err != nil {
		return err
	}

	return Recover(chanBackups, restorer, peerConnector)
}

// UnpackAndRecoverMulti is a one-shot method, that given a set of packed
// multi-channel backups, will restore the channel states to channel shells,
// and also reach out to connect to any of the known node addresses for that
// channel. It is assumes that after this method exists, if a connection we
// able to be established, then then PeerConnector will continue to attempt to
// re-establish a persistent connection in the background.
func UnpackAndRecoverMulti(packedMulti PackedMulti,
	keyChain keychain.KeyRing, restorer ChannelRestorer,
	peerConnector PeerConnector) error {

	chanBackups, err := packedMulti.Unpack(keyChain)
	if err != nil {
		return err
	}

	return Recover(chanBackups.StaticBackups, restorer, peerConnector)
}
