package contractcourt

import (
	"encoding/binary"
	"fmt"
	"io"
	"sync"

	"github.com/btcsuite/btcd/btcutil/v2"
	"github.com/btcsuite/btcd/chainhash/v2"
	"github.com/btcsuite/btcd/txscript/v2"
	"github.com/btcsuite/btcd/wire/v2"
	"github.com/lightningnetwork/lnd/channeldb"
	"github.com/lightningnetwork/lnd/chanstate"
	"github.com/lightningnetwork/lnd/fn/v2"
	"github.com/lightningnetwork/lnd/graph/db/models"
	"github.com/lightningnetwork/lnd/input"
	"github.com/lightningnetwork/lnd/labels"
	"github.com/lightningnetwork/lnd/lnutils"
	"github.com/lightningnetwork/lnd/lnwallet"
	"github.com/lightningnetwork/lnd/sweep"
)

// htlcSuccessResolver is a resolver that's capable of sweeping an incoming
// HTLC output on-chain. If this is the remote party's commitment, we'll sweep
// it directly from the commitment output *immediately*. If this is our
// commitment, we'll first broadcast the success transaction, then send it to
// the incubator for sweeping. That's it, no need to send any clean up
// messages.
//
// TODO(roasbeef): don't need to broadcast?
type htlcSuccessResolver struct {
	// htlcResolution is the incoming HTLC resolution for this HTLC. It
	// contains everything we need to properly resolve this HTLC.
	htlcResolution lnwallet.IncomingHtlcResolution

	// outputIncubating returns true if we've sent the output to the output
	// incubator (utxo nursery). In case the htlcResolution has non-nil
	// SignDetails, it means we will let the Sweeper handle broadcasting
	// the secondd-level transaction, and sweeping its output. In this case
	// we let this field indicate whether we need to broadcast the
	// second-level tx (false) or if it has confirmed and we must sweep the
	// second-level output (true).
	outputIncubating bool

	// broadcastHeight is the height that the original contract was
	// broadcast to the main-chain at. We'll use this value to bound any
	// historical queries to the chain for spends/confirmations.
	broadcastHeight uint32

	// htlc contains information on the htlc that we are resolving on-chain.
	htlc channeldb.HTLC

	// chanType denotes the type of channel the HTLC belongs to.
	chanType channeldb.ChannelType

	// currentReport stores the current state of the resolver for reporting
	// over the rpc interface. This should only be reported in case we have
	// a non-nil SignDetails on the htlcResolution, otherwise the nursery
	// will produce reports.
	currentReport ContractReport

	// reportLock prevents concurrent access to the resolver report.
	reportLock sync.Mutex

	contractResolverKit

	htlcLeaseResolver
}

// newSuccessResolver instanties a new htlc success resolver.
func newSuccessResolver(res lnwallet.IncomingHtlcResolution,
	broadcastHeight uint32, htlc channeldb.HTLC,
	chanType channeldb.ChannelType,
	resCfg ResolverConfig) *htlcSuccessResolver {

	h := &htlcSuccessResolver{
		contractResolverKit: *newContractResolverKit(resCfg),
		htlcResolution:      res,
		broadcastHeight:     broadcastHeight,
		htlc:                htlc,
		chanType:            chanType,
	}

	h.initReport()
	h.initLogger(fmt.Sprintf("%T(%v)", h, h.outpoint()))

	return h
}

// outpoint returns the outpoint of the HTLC output we're attempting to sweep.
func (h *htlcSuccessResolver) outpoint() wire.OutPoint {
	// The primary key for this resolver will be the outpoint of the HTLC
	// on the commitment transaction itself. If this is our commitment,
	// then the output can be found within the signed success tx,
	// otherwise, it's just the ClaimOutpoint.
	if h.htlcResolution.SignedSuccessTx != nil {
		return h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
	}

	return h.htlcResolution.ClaimOutpoint
}

// ResolverKey returns an identifier which should be globally unique for this
// particular resolver within the chain the original contract resides within.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcSuccessResolver) ResolverKey() []byte {
	key := newResolverID(h.outpoint())
	return key[:]
}

// Resolve attempts to resolve an unresolved incoming HTLC that we know the
// preimage to. If the HTLC is on the commitment of the remote party, then we'll
// simply sweep it directly. Otherwise, we'll hand this off to the utxo nursery
// to do its duty. There is no need to make a call to the invoice registry
// anymore. Every HTLC has already passed through the incoming contest resolver
// and in there the invoice was already marked as settled.
//
// NOTE: Part of the ContractResolver interface.
//
// TODO(yy): refactor the interface method to return an error only.
func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
	var err error

	switch {
	// If we're already resolved, then we can exit early.
	case h.IsResolved():
		h.log.Errorf("already resolved")

	// If this is an output on the remote party's commitment transaction,
	// use the direct-spend path to sweep the htlc.
	case h.isRemoteCommitOutput():
		err = h.resolveRemoteCommitOutput()

	// If this is an output on our commitment transaction using post-anchor
	// channel type, it will be handled by the sweeper.
	case h.isZeroFeeOutput():
		err = h.resolveSuccessTx()

	// If this is an output on our own commitment using pre-anchor channel
	// type, we will publish the success tx and offer the output to the
	// nursery.
	default:
		err = h.resolveLegacySuccessTx()
	}

	return nil, err
}

// resolveRemoteCommitOutput handles sweeping an HTLC output on the remote
// commitment with the preimage. In this case we can sweep the output directly,
// and don't have to broadcast a second-level transaction.
func (h *htlcSuccessResolver) resolveRemoteCommitOutput() error {
	h.log.Info("waiting for direct-preimage spend of the htlc to confirm")

	// Wait for the direct-preimage HTLC sweep tx to confirm.
	//
	// TODO(yy): use the result chan returned from `SweepInput`.
	sweepTxDetails, err := waitForSpend(
		&h.htlcResolution.ClaimOutpoint,
		h.htlcResolution.SweepSignDesc.Output.PkScript,
		h.broadcastHeight, h.Notifier, h.quit,
	)
	if err != nil {
		return err
	}

	// TODO(yy): should also update the `RecoveredBalance` and
	// `LimboBalance` like other paths?

	// Checkpoint the resolver, and write the outcome to disk.
	return h.checkpointClaim(sweepTxDetails.SpenderTxHash)
}

// checkpointClaim checkpoints the success resolver with the reports it needs.
// If this htlc was claimed two stages, it will write reports for both stages,
// otherwise it will just write for the single htlc claim.
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash) error {
	// Mark the htlc as final settled.
	err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
		h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, true,
	)
	if err != nil {
		return err
	}

	// Send notification.
	h.ChainArbitratorConfig.HtlcNotifier.NotifyFinalHtlcEvent(
		models.CircuitKey{
			ChanID: h.ShortChanID,
			HtlcID: h.htlc.HtlcIndex,
		},
		channeldb.FinalHtlcInfo{
			Settled:  true,
			Offchain: false,
		},
	)

	// Create a resolver report for claiming of the htlc itself.
	amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
	reports := []*channeldb.ResolverReport{
		{
			OutPoint:        h.htlcResolution.ClaimOutpoint,
			Amount:          amt,
			ResolverType:    channeldb.ResolverTypeIncomingHtlc,
			ResolverOutcome: channeldb.ResolverOutcomeClaimed,
			SpendTxID:       spendTx,
		},
	}

	// If we have a success tx, we append a report to represent our first
	// stage claim.
	if h.htlcResolution.SignedSuccessTx != nil {
		// If the SignedSuccessTx is not nil, we are claiming the htlc
		// in two stages, so we need to create a report for the first
		// stage transaction as well.
		spendTx := h.htlcResolution.SignedSuccessTx
		spendTxID := spendTx.TxHash()

		report := &channeldb.ResolverReport{
			OutPoint:        spendTx.TxIn[0].PreviousOutPoint,
			Amount:          h.htlc.Amt.ToSatoshis(),
			ResolverType:    channeldb.ResolverTypeIncomingHtlc,
			ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
			SpendTxID:       &spendTxID,
		}
		reports = append(reports, report)
	}

	// Finally, we checkpoint the resolver with our report(s).
	h.markResolved()
	return h.Checkpoint(h, reports...)
}

// Stop signals the resolver to cancel any current resolution processes, and
// suspend.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcSuccessResolver) Stop() {
	h.log.Debugf("stopping...")
	defer h.log.Debugf("stopped")

	close(h.quit)
}

// report returns a report on the resolution state of the contract.
func (h *htlcSuccessResolver) report() *ContractReport {
	// If the sign details are nil, the report will be created by handled
	// by the nursery.
	if h.htlcResolution.SignDetails == nil {
		return nil
	}

	h.reportLock.Lock()
	defer h.reportLock.Unlock()
	cpy := h.currentReport
	return &cpy
}

func (h *htlcSuccessResolver) initReport() {
	// We create the initial report. This will only be reported for
	// resolvers not handled by the nursery.
	finalAmt := h.htlc.Amt.ToSatoshis()
	if h.htlcResolution.SignedSuccessTx != nil {
		finalAmt = btcutil.Amount(
			h.htlcResolution.SignedSuccessTx.TxOut[0].Value,
		)
	}

	h.currentReport = ContractReport{
		Outpoint:       h.htlcResolution.ClaimOutpoint,
		Type:           ReportOutputIncomingHtlc,
		Amount:         finalAmt,
		MaturityHeight: h.htlcResolution.CsvDelay,
		LimboBalance:   finalAmt,
		Stage:          1,
	}
}

// Encode writes an encoded version of the ContractResolver into the passed
// Writer.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcSuccessResolver) Encode(w io.Writer) error {
	// First we'll encode our inner HTLC resolution.
	if err := encodeIncomingResolution(w, &h.htlcResolution); err != nil {
		return err
	}

	// Next, we'll write out the fields that are specified to the contract
	// resolver.
	if err := binary.Write(w, endian, h.outputIncubating); err != nil {
		return err
	}
	if err := binary.Write(w, endian, h.IsResolved()); err != nil {
		return err
	}
	if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
		return err
	}
	if _, err := w.Write(h.htlc.RHash[:]); err != nil {
		return err
	}

	// We encode the sign details last for backwards compatibility.
	err := encodeSignDetails(w, h.htlcResolution.SignDetails)
	if err != nil {
		return err
	}

	return nil
}

// newSuccessResolverFromReader attempts to decode an encoded ContractResolver
// from the passed Reader instance, returning an active ContractResolver
// instance.
func newSuccessResolverFromReader(r io.Reader, resCfg ResolverConfig) (
	*htlcSuccessResolver, error) {

	h := &htlcSuccessResolver{
		contractResolverKit: *newContractResolverKit(resCfg),
	}

	// First we'll decode our inner HTLC resolution.
	if err := decodeIncomingResolution(r, &h.htlcResolution); err != nil {
		return nil, err
	}

	// Next, we'll read all the fields that are specified to the contract
	// resolver.
	if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
		return nil, err
	}

	var resolved bool
	if err := binary.Read(r, endian, &resolved); err != nil {
		return nil, err
	}
	if resolved {
		h.markResolved()
	}

	if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
		return nil, err
	}
	if _, err := io.ReadFull(r, h.htlc.RHash[:]); err != nil {
		return nil, err
	}

	// Sign details is a new field that was added to the htlc resolution,
	// so it is serialized last for backwards compatibility. We try to read
	// it, but don't error out if there are not bytes left.
	signDetails, err := decodeSignDetails(r)
	if err == nil {
		h.htlcResolution.SignDetails = signDetails
	} else if err != io.EOF && err != io.ErrUnexpectedEOF {
		return nil, err
	}

	h.initReport()
	h.initLogger(fmt.Sprintf("%T(%v)", h, h.outpoint()))

	return h, nil
}

// Supplement adds additional information to the resolver that is required
// before Resolve() is called.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcSuccessResolver) Supplement(htlc channeldb.HTLC) {
	h.htlc = htlc
}

// HtlcPoint returns the htlc's outpoint on the commitment tx.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcSuccessResolver) HtlcPoint() wire.OutPoint {
	return h.htlcResolution.HtlcPoint()
}

// SupplementState allows the user of a ContractResolver to supplement it with
// state required for the proper resolution of a contract. This restores the
// channel type which is needed to select the correct witness type for
// production taproot channels after restart.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcSuccessResolver) SupplementState(state *chanstate.OpenChannel) {
	h.htlcLeaseResolver.SupplementState(state)
	h.chanType = state.ChanType
}

// SupplementDeadline does nothing for an incoming htlc resolver.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcSuccessResolver) SupplementDeadline(_ fn.Option[int32]) {
}

// A compile time assertion to ensure htlcSuccessResolver meets the
// ContractResolver interface.
var _ htlcContractResolver = (*htlcSuccessResolver)(nil)

// isRemoteCommitOutput returns a bool to indicate whether the htlc output is
// on the remote commitment.
func (h *htlcSuccessResolver) isRemoteCommitOutput() bool {
	// If we don't have a success transaction, then this means that this is
	// an output on the remote party's commitment transaction.
	return h.htlcResolution.SignedSuccessTx == nil
}

// isZeroFeeOutput returns a boolean indicating whether the htlc output is from
// a anchor-enabled channel, which uses the sighash SINGLE|ANYONECANPAY.
func (h *htlcSuccessResolver) isZeroFeeOutput() bool {
	// If we have non-nil SignDetails, this means it has a 2nd level HTLC
	// transaction that is signed using sighash SINGLE|ANYONECANPAY (the
	// case for anchor type channels). In this case we can re-sign it and
	// attach fees at will.
	return h.htlcResolution.SignedSuccessTx != nil &&
		h.htlcResolution.SignDetails != nil
}

// isTaproot returns true if the resolver is for a taproot output.
func (h *htlcSuccessResolver) isTaproot() bool {
	return txscript.IsPayToTaproot(
		h.htlcResolution.SweepSignDesc.Output.PkScript,
	)
}

// isTaprootFinal returns true if the htlc output is from a final taproot
// channel.
func (h *htlcSuccessResolver) isTaprootFinal() bool {
	return h.chanType.IsTaprootFinal()
}

// sweepRemoteCommitOutput creates a sweep request to sweep the HTLC output on
// the remote commitment via the direct preimage-spend.
func (h *htlcSuccessResolver) sweepRemoteCommitOutput() error {
	// Before we can craft out sweeping transaction, we need to create an
	// input which contains all the items required to add this input to a
	// sweeping transaction, and generate a witness.
	var inp input.Input

	switch {
	case h.isTaprootFinal():
		inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInputFinal(
			&h.htlcResolution.ClaimOutpoint,
			&h.htlcResolution.SweepSignDesc,
			h.htlcResolution.Preimage[:],
			h.broadcastHeight,
			h.htlcResolution.CsvDelay,
			input.WithResolutionBlob(
				h.htlcResolution.ResolutionBlob,
			),
		))
	case h.isTaproot():
		inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
			&h.htlcResolution.ClaimOutpoint,
			&h.htlcResolution.SweepSignDesc,
			h.htlcResolution.Preimage[:],
			h.broadcastHeight,
			h.htlcResolution.CsvDelay,
			input.WithResolutionBlob(
				h.htlcResolution.ResolutionBlob,
			),
		))
	default:
		inp = lnutils.Ptr(input.MakeHtlcSucceedInput(
			&h.htlcResolution.ClaimOutpoint,
			&h.htlcResolution.SweepSignDesc,
			h.htlcResolution.Preimage[:],
			h.broadcastHeight,
			h.htlcResolution.CsvDelay,
		))
	}

	// Calculate the budget for this sweep.
	budget := calculateBudget(
		btcutil.Amount(inp.SignDesc().Output.Value),
		h.Budget.DeadlineHTLCRatio,
		h.Budget.DeadlineHTLC,
	)

	deadline := fn.Some(int32(h.htlc.RefundTimeout))

	log.Infof("%T(%x): offering direct-preimage HTLC output to sweeper "+
		"with deadline=%v, budget=%v", h, h.htlc.RHash[:],
		h.htlc.RefundTimeout, budget)

	// We'll now offer the direct preimage HTLC to the sweeper.
	_, err := h.Sweeper.SweepInput(
		inp,
		sweep.Params{
			Budget:         budget,
			DeadlineHeight: deadline,
		},
	)

	return err
}

// sweepSuccessTx attempts to sweep the second level success tx.
func (h *htlcSuccessResolver) sweepSuccessTx() error {
	var secondLevelInput input.HtlcSecondLevelAnchorInput
	if h.isTaproot() {
		secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput(
			h.htlcResolution.SignedSuccessTx,
			h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
			h.broadcastHeight, input.WithResolutionBlob(
				h.htlcResolution.ResolutionBlob,
			),
		)
	} else {
		secondLevelInput = input.MakeHtlcSecondLevelSuccessAnchorInput(
			h.htlcResolution.SignedSuccessTx,
			h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
			h.broadcastHeight,
		)
	}

	// Calculate the budget for this sweep.
	value := btcutil.Amount(secondLevelInput.SignDesc().Output.Value)
	budget := calculateBudget(
		value, h.Budget.DeadlineHTLCRatio, h.Budget.DeadlineHTLC,
	)

	// The deadline would be the CLTV in this HTLC output. If we are the
	// initiator of this force close, with the default
	// `IncomingBroadcastDelta`, it means we have 10 blocks left when going
	// onchain.
	deadline := fn.Some(int32(h.htlc.RefundTimeout))

	h.log.Infof("offering second-level HTLC success tx to sweeper with "+
		"deadline=%v, budget=%v", h.htlc.RefundTimeout, budget)

	// We'll now offer the second-level transaction to the sweeper.
	_, err := h.Sweeper.SweepInput(
		&secondLevelInput,
		sweep.Params{
			Budget:         budget,
			DeadlineHeight: deadline,
		},
	)

	return err
}

// sweepSuccessTxOutput attempts to sweep the output of the second level
// success tx.
func (h *htlcSuccessResolver) sweepSuccessTxOutput() error {
	h.log.Debugf("sweeping output %v from 2nd-level HTLC success tx",
		h.htlcResolution.ClaimOutpoint)

	// This should be non-blocking as we will only attempt to sweep the
	// output when the second level tx has already been confirmed. In other
	// words, waitForSpend will return immediately.
	commitSpend, err := waitForSpend(
		&h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
		h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
		h.broadcastHeight, h.Notifier, h.quit,
	)
	if err != nil {
		return err
	}

	// The HTLC success tx has a CSV lock that we must wait for, and if
	// this is a lease enforced channel and we're the imitator, we may need
	// to wait for longer.
	waitHeight := h.deriveWaitHeight(h.htlcResolution.CsvDelay, commitSpend)

	// Now that the sweeper has broadcasted the second-level transaction,
	// it has confirmed, and we have checkpointed our state, we'll sweep
	// the second level output. We report the resolver has moved the next
	// stage.
	h.reportLock.Lock()
	h.currentReport.Stage = 2
	h.currentReport.MaturityHeight = waitHeight
	h.reportLock.Unlock()

	if h.hasCLTV() {
		log.Infof("%T(%x): waiting for CSV and CLTV lock to expire at "+
			"height %v", h, h.htlc.RHash[:], waitHeight)
	} else {
		log.Infof("%T(%x): waiting for CSV lock to expire at height %v",
			h, h.htlc.RHash[:], waitHeight)
	}

	// We'll use this input index to determine the second-level output
	// index on the transaction, as the signatures requires the indexes to
	// be the same. We don't look for the second-level output script
	// directly, as there might be more than one HTLC output to the same
	// pkScript.
	op := &wire.OutPoint{
		Hash:  *commitSpend.SpenderTxHash,
		Index: commitSpend.SpenderInputIndex,
	}

	// Let the sweeper sweep the second-level output now that the
	// CSV/CLTV locks have expired.
	var witType input.StandardWitnessType
	switch {
	case h.isTaprootFinal():
		witType = input.TaprootHtlcAcceptedSuccessSecondLevelFinal
	case h.isTaproot():
		witType = input.TaprootHtlcAcceptedSuccessSecondLevel
	default:
		witType = input.HtlcAcceptedSuccessSecondLevel
	}
	inp := h.makeSweepInput(
		op, witType,
		input.LeaseHtlcAcceptedSuccessSecondLevel,
		&h.htlcResolution.SweepSignDesc,
		h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight),
		h.htlc.RHash, h.htlcResolution.ResolutionBlob,
	)

	// Calculate the budget for this sweep.
	budget := calculateBudget(
		btcutil.Amount(inp.SignDesc().Output.Value),
		h.Budget.NoDeadlineHTLCRatio,
		h.Budget.NoDeadlineHTLC,
	)

	log.Infof("%T(%x): offering second-level success tx output to sweeper "+
		"with no deadline and budget=%v at height=%v", h,
		h.htlc.RHash[:], budget, waitHeight)

	// TODO(yy): use the result chan returned from SweepInput.
	_, err = h.Sweeper.SweepInput(
		inp,
		sweep.Params{
			Budget: budget,

			// For second level success tx, there's no rush to get
			// it confirmed, so we use a nil deadline.
			DeadlineHeight: fn.None[int32](),
		},
	)

	return err
}

// resolveLegacySuccessTx handles an HTLC output from a pre-anchor type channel
// by broadcasting the second-level success transaction.
func (h *htlcSuccessResolver) resolveLegacySuccessTx() error {
	// Otherwise we'll publish the second-level transaction directly and
	// offer the resolution to the nursery to handle.
	h.log.Infof("broadcasting legacy second-level success tx: %v",
		h.htlcResolution.SignedSuccessTx.TxHash())

	// We'll now broadcast the second layer transaction so we can kick off
	// the claiming process.
	//
	// TODO(yy): offer it to the sweeper instead.
	label := labels.MakeLabel(
		labels.LabelTypeChannelClose, &h.ShortChanID,
	)
	err := h.PublishTx(h.htlcResolution.SignedSuccessTx, label)
	if err != nil {
		return err
	}

	// Fast-forward to resolve the output from the success tx if the it has
	// already been sent to the UtxoNursery.
	if h.outputIncubating {
		return h.resolveSuccessTxOutput(h.htlcResolution.ClaimOutpoint)
	}

	h.log.Infof("incubating incoming htlc output")

	// Send the output to the incubator.
	err = h.IncubateOutputs(
		h.ChanPoint, fn.None[lnwallet.OutgoingHtlcResolution](),
		fn.Some(h.htlcResolution),
		h.broadcastHeight, fn.Some(int32(h.htlc.RefundTimeout)),
		WithChanType(h.chanType),
	)
	if err != nil {
		return err
	}

	// Mark the output as incubating and checkpoint it.
	h.outputIncubating = true
	if err := h.Checkpoint(h); err != nil {
		return err
	}

	// Move to resolve the output.
	return h.resolveSuccessTxOutput(h.htlcResolution.ClaimOutpoint)
}

// resolveSuccessTx waits for the sweeping tx of the second-level success tx to
// confirm and offers the output from the success tx to the sweeper.
func (h *htlcSuccessResolver) resolveSuccessTx() error {
	h.log.Infof("waiting for 2nd-level HTLC success transaction to confirm")

	// Create aliases to make the code more readable.
	outpoint := h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
	pkScript := h.htlcResolution.SignDetails.SignDesc.Output.PkScript

	// Wait for the second level transaction to confirm.
	commitSpend, err := waitForSpend(
		&outpoint, pkScript, h.broadcastHeight, h.Notifier, h.quit,
	)
	if err != nil {
		return err
	}

	// We'll use this input index to determine the second-level output
	// index on the transaction, as the signatures requires the indexes to
	// be the same. We don't look for the second-level output script
	// directly, as there might be more than one HTLC output to the same
	// pkScript.
	op := wire.OutPoint{
		Hash:  *commitSpend.SpenderTxHash,
		Index: commitSpend.SpenderInputIndex,
	}

	// If the 2nd-stage sweeping has already been started, we can
	// fast-forward to start the resolving process for the stage two
	// output.
	if h.outputIncubating {
		return h.resolveSuccessTxOutput(op)
	}

	// Now that the second-level transaction has confirmed, we checkpoint
	// the state so we'll go to the next stage in case of restarts.
	h.outputIncubating = true
	if err := h.Checkpoint(h); err != nil {
		log.Errorf("unable to Checkpoint: %v", err)
		return err
	}

	h.log.Infof("2nd-level HTLC success tx=%v confirmed",
		commitSpend.SpenderTxHash)

	// Send the sweep request for the output from the success tx.
	if err := h.sweepSuccessTxOutput(); err != nil {
		return err
	}

	return h.resolveSuccessTxOutput(op)
}

// resolveSuccessTxOutput waits for the spend of the output from the 2nd-level
// success tx.
func (h *htlcSuccessResolver) resolveSuccessTxOutput(op wire.OutPoint) error {
	// To wrap this up, we'll wait until the second-level transaction has
	// been spent, then fully resolve the contract.
	log.Infof("%T(%x): waiting for second-level HTLC output to be spent "+
		"after csv_delay=%v", h, h.htlc.RHash[:],
		h.htlcResolution.CsvDelay)

	spend, err := waitForSpend(
		&op, h.htlcResolution.SweepSignDesc.Output.PkScript,
		h.broadcastHeight, h.Notifier, h.quit,
	)
	if err != nil {
		return err
	}

	h.reportLock.Lock()
	h.currentReport.RecoveredBalance = h.currentReport.LimboBalance
	h.currentReport.LimboBalance = 0
	h.reportLock.Unlock()

	return h.checkpointClaim(spend.SpenderTxHash)
}

// Launch creates an input based on the details of the incoming htlc resolution
// and offers it to the sweeper.
func (h *htlcSuccessResolver) Launch() error {
	if h.isLaunched() {
		h.log.Tracef("already launched")
		return nil
	}

	h.log.Debugf("launching resolver...")
	h.markLaunched()

	switch {
	// If we're already resolved, then we can exit early.
	case h.IsResolved():
		h.log.Errorf("already resolved")
		return nil

	// If this is an output on the remote party's commitment transaction,
	// use the direct-spend path.
	case h.isRemoteCommitOutput():
		return h.sweepRemoteCommitOutput()

	// If this is an anchor type channel, we now sweep either the
	// second-level success tx or the output from the second-level success
	// tx.
	case h.isZeroFeeOutput():
		// If the second-level success tx has already been swept, we
		// can go ahead and sweep its output.
		if h.outputIncubating {
			return h.sweepSuccessTxOutput()
		}

		// Otherwise, sweep the second level tx.
		return h.sweepSuccessTx()

	// If this is a legacy channel type, the output is handled by the
	// nursery via the Resolve so we do nothing here.
	//
	// TODO(yy): handle the legacy output by offering it to the sweeper.
	default:
		return nil
	}
}
