package lnwallet

import (
	"github.com/btcsuite/btcd/btcutil"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/wire"
	"github.com/lightningnetwork/lnd/channeldb"
	"github.com/lightningnetwork/lnd/fn"
	"github.com/lightningnetwork/lnd/input"
	"github.com/lightningnetwork/lnd/lntypes"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/lightningnetwork/lnd/tlv"
)

// CommitSortFunc is a function type alias for a function that sorts the
// commitment transaction outputs. The second parameter is a list of CLTV
// timeouts that must correspond to the number of transaction outputs, with the
// value of 0 for non-HTLC outputs. The HTLC indexes are needed to have a
// deterministic sort value for HTLCs that have the identical amount, CLTV
// timeout and payment hash (e.g. multiple MPP shards of the same payment, where
// the on-chain script would be identical).
type CommitSortFunc func(tx *wire.MsgTx, cltvs []uint32,
	indexes []input.HtlcIndex) error

// DefaultCommitSort is the default commitment sort function that sorts the
// commitment transaction inputs and outputs according to BIP69. The second
// parameter is a list of CLTV timeouts that must correspond to the number of
// transaction outputs, with the value of 0 for non-HTLC outputs. The third
// parameter is unused for the default sort function.
func DefaultCommitSort(tx *wire.MsgTx, cltvs []uint32,
	_ []input.HtlcIndex) error {

	InPlaceCommitSort(tx, cltvs)
	return nil
}

// CommitAuxLeaves stores two potential auxiliary leaves for the remote and
// local output that may be used to augment the final tapscript trees of the
// commitment transaction.
type CommitAuxLeaves struct {
	// LocalAuxLeaf is the local party's auxiliary leaf.
	LocalAuxLeaf input.AuxTapLeaf

	// RemoteAuxLeaf is the remote party's auxiliary leaf.
	RemoteAuxLeaf input.AuxTapLeaf

	// OutgoingHTLCLeaves is the set of aux leaves for the outgoing HTLCs
	// on this commitment transaction.
	OutgoingHtlcLeaves input.HtlcAuxLeaves

	// IncomingHTLCLeaves is the set of aux leaves for the incoming HTLCs
	// on this commitment transaction.
	IncomingHtlcLeaves input.HtlcAuxLeaves
}

// AuxChanState is a struct that holds certain fields of the
// channeldb.OpenChannel struct that are used by the aux components. The data
// is copied over to prevent accidental mutation of the original channel state.
type AuxChanState struct {
	// ChanType denotes which type of channel this is.
	ChanType channeldb.ChannelType

	// FundingOutpoint is the outpoint of the final funding transaction.
	// This value uniquely and globally identifies the channel within the
	// target blockchain as specified by the chain hash parameter.
	FundingOutpoint wire.OutPoint

	// ShortChannelID encodes the exact location in the chain in which the
	// channel was initially confirmed. This includes: the block height,
	// transaction index, and the output within the target transaction.
	//
	// If IsZeroConf(), then this will the "base" (very first) ALIAS scid
	// and the confirmed SCID will be stored in ConfirmedScid.
	ShortChannelID lnwire.ShortChannelID

	// IsInitiator is a bool which indicates if we were the original
	// initiator for the channel. This value may affect how higher levels
	// negotiate fees, or close the channel.
	IsInitiator bool

	// Capacity is the total capacity of this channel.
	Capacity btcutil.Amount

	// LocalChanCfg is the channel configuration for the local node.
	LocalChanCfg channeldb.ChannelConfig

	// RemoteChanCfg is the channel configuration for the remote node.
	RemoteChanCfg channeldb.ChannelConfig

	// ThawHeight is the height when a frozen channel once again becomes a
	// normal channel. If this is zero, then there're no restrictions on
	// this channel. If the value is lower than 500,000, then it's
	// interpreted as a relative height, or an absolute height otherwise.
	ThawHeight uint32

	// TapscriptRoot is an optional tapscript root used to derive the MuSig2
	// funding output.
	TapscriptRoot fn.Option[chainhash.Hash]

	// CustomBlob is an optional blob that can be used to store information
	// specific to a custom channel type. This information is only created
	// at channel funding time, and after wards is to be considered
	// immutable.
	CustomBlob fn.Option[tlv.Blob]
}

// NewAuxChanState creates a new AuxChanState from the given channel state.
func NewAuxChanState(chanState *channeldb.OpenChannel) AuxChanState {
	return AuxChanState{
		ChanType:        chanState.ChanType,
		FundingOutpoint: chanState.FundingOutpoint,
		ShortChannelID:  chanState.ShortChannelID,
		IsInitiator:     chanState.IsInitiator,
		Capacity:        chanState.Capacity,
		LocalChanCfg:    chanState.LocalChanCfg,
		RemoteChanCfg:   chanState.RemoteChanCfg,
		ThawHeight:      chanState.ThawHeight,
		TapscriptRoot:   chanState.TapscriptRoot,
		CustomBlob:      chanState.CustomBlob,
	}
}

// CommitDiffAuxInput is the input required to compute the diff of the auxiliary
// leaves for a commitment transaction.
type CommitDiffAuxInput struct {
	// ChannelState is the static channel information of the channel this
	// commitment transaction relates to.
	ChannelState AuxChanState

	// PrevBlob is the blob of the previous commitment transaction.
	PrevBlob tlv.Blob

	// UnfilteredView is the unfiltered, original HTLC view of the channel.
	// Unfiltered in this context means that the view contains all HTLCs,
	// including the canceled ones.
	UnfilteredView *HtlcView

	// WhoseCommit denotes whose commitment transaction we are computing the
	// diff for.
	WhoseCommit lntypes.ChannelParty

	// OurBalance is the balance of the local party.
	OurBalance lnwire.MilliSatoshi

	// TheirBalance is the balance of the remote party.
	TheirBalance lnwire.MilliSatoshi

	// KeyRing is the key ring that can be used to derive keys for the
	// commitment transaction.
	KeyRing CommitmentKeyRing
}

// CommitDiffAuxResult is the result of computing the diff of the auxiliary
// leaves for a commitment transaction.
type CommitDiffAuxResult struct {
	// AuxLeaves are the auxiliary leaves for the new commitment
	// transaction.
	AuxLeaves fn.Option[CommitAuxLeaves]

	// CommitSortFunc is an optional function that sorts the commitment
	// transaction inputs and outputs.
	CommitSortFunc fn.Option[CommitSortFunc]
}

// AuxLeafStore is used to optionally fetch auxiliary tapscript leaves for the
// commitment transaction given an opaque blob. This is also used to implement
// a state transition function for the blobs to allow them to be refreshed with
// each state.
type AuxLeafStore interface {
	// FetchLeavesFromView attempts to fetch the auxiliary leaves that
	// correspond to the passed aux blob, and pending original (unfiltered)
	// HTLC view.
	FetchLeavesFromView(
		in CommitDiffAuxInput) fn.Result[CommitDiffAuxResult]

	// FetchLeavesFromCommit attempts to fetch the auxiliary leaves that
	// correspond to the passed aux blob, and an existing channel
	// commitment.
	FetchLeavesFromCommit(chanState AuxChanState,
		commit channeldb.ChannelCommitment,
		keyRing CommitmentKeyRing) fn.Result[CommitDiffAuxResult]

	// FetchLeavesFromRevocation attempts to fetch the auxiliary leaves
	// from a channel revocation that stores balance + blob information.
	FetchLeavesFromRevocation(
		r *channeldb.RevocationLog) fn.Result[CommitDiffAuxResult]

	// ApplyHtlcView serves as the state transition function for the custom
	// channel's blob. Given the old blob, and an HTLC view, then a new
	// blob should be returned that reflects the pending updates.
	ApplyHtlcView(in CommitDiffAuxInput) fn.Result[fn.Option[tlv.Blob]]
}

// auxLeavesFromView is used to derive the set of commit aux leaves (if any),
// that are needed to create a new commitment transaction using the original
// (unfiltered) htlc view.
func auxLeavesFromView(leafStore AuxLeafStore, chanState *channeldb.OpenChannel,
	prevBlob fn.Option[tlv.Blob], originalView *HtlcView,
	whoseCommit lntypes.ChannelParty, ourBalance,
	theirBalance lnwire.MilliSatoshi,
	keyRing CommitmentKeyRing) fn.Result[CommitDiffAuxResult] {

	return fn.MapOptionZ(
		prevBlob, func(blob tlv.Blob) fn.Result[CommitDiffAuxResult] {
			return leafStore.FetchLeavesFromView(CommitDiffAuxInput{
				ChannelState:   NewAuxChanState(chanState),
				PrevBlob:       blob,
				UnfilteredView: originalView,
				WhoseCommit:    whoseCommit,
				OurBalance:     ourBalance,
				TheirBalance:   theirBalance,
				KeyRing:        keyRing,
			})
		},
	)
}

// updateAuxBlob is a helper function that attempts to update the aux blob
// given the prior and current state information.
func updateAuxBlob(leafStore AuxLeafStore, chanState *channeldb.OpenChannel,
	prevBlob fn.Option[tlv.Blob], nextViewUnfiltered *HtlcView,
	whoseCommit lntypes.ChannelParty, ourBalance,
	theirBalance lnwire.MilliSatoshi,
	keyRing CommitmentKeyRing) fn.Result[fn.Option[tlv.Blob]] {

	return fn.MapOptionZ(
		prevBlob, func(blob tlv.Blob) fn.Result[fn.Option[tlv.Blob]] {
			return leafStore.ApplyHtlcView(CommitDiffAuxInput{
				ChannelState:   NewAuxChanState(chanState),
				PrevBlob:       blob,
				UnfilteredView: nextViewUnfiltered,
				WhoseCommit:    whoseCommit,
				OurBalance:     ourBalance,
				TheirBalance:   theirBalance,
				KeyRing:        keyRing,
			})
		},
	)
}
