package input

import (
	"fmt"

	"github.com/btcsuite/btcd/btcutil"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
	"github.com/lightningnetwork/lnd/fn"
	"github.com/lightningnetwork/lnd/lntypes"
	"github.com/lightningnetwork/lnd/tlv"
)

// EmptyOutPoint is a zeroed outpoint.
var EmptyOutPoint wire.OutPoint

// Input represents an abstract UTXO which is to be spent using a sweeping
// transaction. The method provided give the caller all information needed to
// construct a valid input within a sweeping transaction to sweep this
// lingering UTXO.
type Input interface {
	// Outpoint returns the reference to the output being spent, used to
	// construct the corresponding transaction input.
	OutPoint() wire.OutPoint

	// RequiredTxOut returns a non-nil TxOut if input commits to a certain
	// transaction output. This is used in the SINGLE|ANYONECANPAY case to
	// make sure any presigned input is still valid by including the
	// output.
	RequiredTxOut() *wire.TxOut

	// RequiredLockTime returns whether this input commits to a tx locktime
	// that must be used in the transaction including it.
	RequiredLockTime() (uint32, bool)

	// WitnessType returns an enum specifying the type of witness that must
	// be generated in order to spend this output.
	WitnessType() WitnessType

	// SignDesc returns a reference to a spendable output's sign
	// descriptor, which is used during signing to compute a valid witness
	// that spends this output.
	SignDesc() *SignDescriptor

	// CraftInputScript returns a valid set of input scripts allowing this
	// output to be spent. The returns input scripts should target the
	// input at location txIndex within the passed transaction. The input
	// scripts generated by this method support spending p2wkh, p2wsh, and
	// also nested p2sh outputs.
	CraftInputScript(signer Signer, txn *wire.MsgTx,
		hashCache *txscript.TxSigHashes,
		prevOutputFetcher txscript.PrevOutputFetcher,
		txinIdx int) (*Script, error)

	// BlocksToMaturity returns the relative timelock, as a number of
	// blocks, that must be built on top of the confirmation height before
	// the output can be spent. For non-CSV locked inputs this is always
	// zero.
	BlocksToMaturity() uint32

	// HeightHint returns the minimum height at which a confirmed spending
	// tx can occur.
	HeightHint() uint32

	// UnconfParent returns information about a possibly unconfirmed parent
	// tx.
	UnconfParent() *TxInfo

	// ResolutionBlob returns a special opaque blob to be used to
	// sweep/resolve this input.
	ResolutionBlob() fn.Option[tlv.Blob]
}

// TxInfo describes properties of a parent tx that are relevant for CPFP.
type TxInfo struct {
	// Fee is the fee of the tx.
	Fee btcutil.Amount

	// Weight is the weight of the tx.
	Weight lntypes.WeightUnit
}

// String returns a human readable version of the tx info.
func (t *TxInfo) String() string {
	return fmt.Sprintf("fee=%v, weight=%v", t.Fee, t.Weight)
}

// SignDetails is a struct containing information needed to resign certain
// inputs. It is used to re-sign 2nd level HTLC transactions that uses the
// SINGLE|ANYONECANPAY sighash type, as we have a signature provided by our
// peer, but we can aggregate multiple of these 2nd level transactions into a
// new transaction, that needs to be signed by us.
type SignDetails struct {
	// SignDesc is the sign descriptor needed for us to sign the input.
	SignDesc SignDescriptor

	// PeerSig is the peer's signature for this input.
	PeerSig Signature

	// SigHashType is the sighash signed by the peer.
	SigHashType txscript.SigHashType
}

type inputKit struct {
	outpoint        wire.OutPoint
	witnessType     WitnessType
	signDesc        SignDescriptor
	heightHint      uint32
	blockToMaturity uint32
	cltvExpiry      uint32

	// unconfParent contains information about a potential unconfirmed
	// parent transaction.
	unconfParent *TxInfo

	// resolutionBlob is an optional blob that can be used to resolve an
	// input.
	resolutionBlob fn.Option[tlv.Blob]
}

// OutPoint returns the breached output's identifier that is to be included as
// a transaction input.
func (i *inputKit) OutPoint() wire.OutPoint {
	return i.outpoint
}

// RequiredTxOut returns a nil for the base input type.
func (i *inputKit) RequiredTxOut() *wire.TxOut {
	return nil
}

// RequiredLockTime returns whether this input commits to a tx locktime that
// must be used in the transaction including it. This will be false for the
// base input type since we can re-sign for any lock time.
func (i *inputKit) RequiredLockTime() (uint32, bool) {
	return i.cltvExpiry, i.cltvExpiry > 0
}

// WitnessType returns the type of witness that must be generated to spend the
// breached output.
func (i *inputKit) WitnessType() WitnessType {
	return i.witnessType
}

// SignDesc returns the breached output's SignDescriptor, which is used during
// signing to compute the witness.
func (i *inputKit) SignDesc() *SignDescriptor {
	return &i.signDesc
}

// HeightHint returns the minimum height at which a confirmed spending
// tx can occur.
func (i *inputKit) HeightHint() uint32 {
	return i.heightHint
}

// BlocksToMaturity returns the relative timelock, as a number of blocks, that
// must be built on top of the confirmation height before the output can be
// spent. For non-CSV locked inputs this is always zero.
func (i *inputKit) BlocksToMaturity() uint32 {
	return i.blockToMaturity
}

// Cpfp returns information about a possibly unconfirmed parent tx.
func (i *inputKit) UnconfParent() *TxInfo {
	return i.unconfParent
}

// ResolutionBlob returns a special opaque blob to be used to sweep/resolve
// this input.
func (i *inputKit) ResolutionBlob() fn.Option[tlv.Blob] {
	return i.resolutionBlob
}

// inputOpts contains options for constructing a new input.
type inputOpts struct {
	// resolutionBlob is an optional blob that can be used to resolve an
	// input.
	resolutionBlob fn.Option[tlv.Blob]
}

// defaultInputOpts returns a new inputOpts with default values.
func defaultInputOpts() *inputOpts {
	return &inputOpts{}
}

// InputOpt is a functional option that can be used to modify the default input
// options.
type InputOpt func(*inputOpts) //nolint:revive

// WithResolutionBlob is an option that can be used to set a resolution blob on
// for an input.
func WithResolutionBlob(b fn.Option[tlv.Blob]) InputOpt {
	return func(o *inputOpts) {
		o.resolutionBlob = b
	}
}

// BaseInput contains all the information needed to sweep a basic
// output (CSV/CLTV/no time lock).
type BaseInput struct {
	inputKit
}

// MakeBaseInput assembles a new BaseInput that can be used to construct a
// sweep transaction.
func MakeBaseInput(outpoint *wire.OutPoint, witnessType WitnessType,
	signDescriptor *SignDescriptor, heightHint uint32,
	unconfParent *TxInfo, opts ...InputOpt) BaseInput {

	opt := defaultInputOpts()
	for _, optF := range opts {
		optF(opt)
	}

	return BaseInput{
		inputKit{
			outpoint:       *outpoint,
			witnessType:    witnessType,
			signDesc:       *signDescriptor,
			heightHint:     heightHint,
			unconfParent:   unconfParent,
			resolutionBlob: opt.resolutionBlob,
		},
	}
}

// NewBaseInput allocates and assembles a new *BaseInput that can be used to
// construct a sweep transaction.
func NewBaseInput(outpoint *wire.OutPoint, witnessType WitnessType,
	signDescriptor *SignDescriptor, heightHint uint32,
	opts ...InputOpt) *BaseInput {

	input := MakeBaseInput(
		outpoint, witnessType, signDescriptor, heightHint, nil, opts...,
	)

	return &input
}

// NewCsvInput assembles a new csv-locked input that can be used to
// construct a sweep transaction.
func NewCsvInput(outpoint *wire.OutPoint, witnessType WitnessType,
	signDescriptor *SignDescriptor, heightHint uint32,
	blockToMaturity uint32, opts ...InputOpt) *BaseInput {

	input := MakeBaseInput(
		outpoint, witnessType, signDescriptor, heightHint, nil, opts...,
	)

	input.blockToMaturity = blockToMaturity

	return &input
}

// NewCsvInputWithCltv assembles a new csv and cltv locked input that can be
// used to construct a sweep transaction.
func NewCsvInputWithCltv(outpoint *wire.OutPoint, witnessType WitnessType,
	signDescriptor *SignDescriptor, heightHint uint32,
	csvDelay uint32, cltvExpiry uint32, opts ...InputOpt) *BaseInput {

	input := MakeBaseInput(
		outpoint, witnessType, signDescriptor, heightHint, nil, opts...,
	)

	input.blockToMaturity = csvDelay
	input.cltvExpiry = cltvExpiry

	return &input
}

// CraftInputScript returns a valid set of input scripts allowing this output
// to be spent. The returned input scripts should target the input at location
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (bi *BaseInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
	hashCache *txscript.TxSigHashes,
	prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
	error) {

	signDesc := bi.SignDesc()
	signDesc.PrevOutputFetcher = prevOutputFetcher
	witnessFunc := bi.witnessType.WitnessGenerator(signer, signDesc)

	return witnessFunc(txn, hashCache, txinIdx)
}

// HtlcSucceedInput constitutes a sweep input that needs a pre-image. The input
// is expected to reside on the commitment tx of the remote party and should
// not be a second level tx output.
type HtlcSucceedInput struct {
	inputKit

	preimage []byte
}

// MakeHtlcSucceedInput assembles a new redeem input that can be used to
// construct a sweep transaction.
func MakeHtlcSucceedInput(outpoint *wire.OutPoint,
	signDescriptor *SignDescriptor, preimage []byte, heightHint,
	blocksToMaturity uint32, opts ...InputOpt) HtlcSucceedInput {

	input := MakeBaseInput(
		outpoint, HtlcAcceptedRemoteSuccess, signDescriptor,
		heightHint, nil, opts...,
	)
	input.blockToMaturity = blocksToMaturity

	return HtlcSucceedInput{
		inputKit: input.inputKit,
		preimage: preimage,
	}
}

// MakeTaprootHtlcSucceedInput creates a new HtlcSucceedInput that can be used
// to spend an HTLC output for a taproot channel on the remote party's
// commitment transaction.
func MakeTaprootHtlcSucceedInput(op *wire.OutPoint, signDesc *SignDescriptor,
	preimage []byte, heightHint, blocksToMaturity uint32,
	opts ...InputOpt) HtlcSucceedInput {

	input := MakeBaseInput(
		op, TaprootHtlcAcceptedRemoteSuccess, signDesc,
		heightHint, nil, opts...,
	)
	input.blockToMaturity = blocksToMaturity

	return HtlcSucceedInput{
		inputKit: input.inputKit,
		preimage: preimage,
	}
}

// CraftInputScript returns a valid set of input scripts allowing this output
// to be spent. The returns input scripts should target the input at location
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (h *HtlcSucceedInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
	hashCache *txscript.TxSigHashes,
	prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
	error) {

	desc := h.signDesc
	desc.SigHashes = hashCache
	desc.InputIndex = txinIdx
	desc.PrevOutputFetcher = prevOutputFetcher

	isTaproot := txscript.IsPayToTaproot(desc.Output.PkScript)

	var (
		witness wire.TxWitness
		err     error
	)
	if isTaproot {
		if desc.ControlBlock == nil {
			return nil, fmt.Errorf("ctrl block must be set")
		}

		desc.SignMethod = TaprootScriptSpendSignMethod

		witness, err = SenderHTLCScriptTaprootRedeem(
			signer, &desc, txn, h.preimage, nil, nil,
		)
	} else {
		witness, err = SenderHtlcSpendRedeem(
			signer, &desc, txn, h.preimage,
		)
	}
	if err != nil {
		return nil, err
	}

	return &Script{
		Witness: witness,
	}, nil
}

// HtlcSecondLevelAnchorInput is an input type used to spend HTLC outputs
// using a re-signed second level transaction, either via the timeout or success
// paths.
type HtlcSecondLevelAnchorInput struct {
	inputKit

	// SignedTx is the original second level transaction signed by the
	// channel peer.
	SignedTx *wire.MsgTx

	// createWitness creates a witness allowing the passed transaction to
	// spend the input.
	createWitness func(signer Signer, txn *wire.MsgTx,
		hashCache *txscript.TxSigHashes,
		prevOutputFetcher txscript.PrevOutputFetcher,
		txinIdx int) (wire.TxWitness, error)
}

// RequiredTxOut returns the tx out needed to be present on the sweep tx for
// the spend of the input to be valid.
func (i *HtlcSecondLevelAnchorInput) RequiredTxOut() *wire.TxOut {
	return i.SignedTx.TxOut[0]
}

// RequiredLockTime returns the locktime needed for the sweep tx for the spend
// of the input to be valid. For a second level HTLC timeout this will be the
// CLTV expiry, for HTLC success it will be zero.
func (i *HtlcSecondLevelAnchorInput) RequiredLockTime() (uint32, bool) {
	return i.SignedTx.LockTime, true
}

// CraftInputScript returns a valid set of input scripts allowing this output
// to be spent. The returns input scripts should target the input at location
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (i *HtlcSecondLevelAnchorInput) CraftInputScript(signer Signer,
	txn *wire.MsgTx, hashCache *txscript.TxSigHashes,
	prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
	error) {

	witness, err := i.createWitness(
		signer, txn, hashCache, prevOutputFetcher, txinIdx,
	)
	if err != nil {
		return nil, err
	}

	return &Script{
		Witness: witness,
	}, nil
}

// MakeHtlcSecondLevelTimeoutAnchorInput creates an input allowing the sweeper
// to spend the HTLC output on our commit using the second level timeout
// transaction.
func MakeHtlcSecondLevelTimeoutAnchorInput(signedTx *wire.MsgTx,
	signDetails *SignDetails, heightHint uint32,
	opts ...InputOpt) HtlcSecondLevelAnchorInput {

	// Spend an HTLC output on our local commitment tx using the
	// 2nd timeout transaction.
	createWitness := func(signer Signer, txn *wire.MsgTx,
		hashCache *txscript.TxSigHashes,
		prevOutputFetcher txscript.PrevOutputFetcher,
		txinIdx int) (wire.TxWitness, error) {

		desc := signDetails.SignDesc
		desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher)
		desc.InputIndex = txinIdx
		desc.PrevOutputFetcher = prevOutputFetcher

		return SenderHtlcSpendTimeout(
			signDetails.PeerSig, signDetails.SigHashType, signer,
			&desc, txn,
		)
	}

	input := MakeBaseInput(
		&signedTx.TxIn[0].PreviousOutPoint,
		HtlcOfferedTimeoutSecondLevelInputConfirmed,
		&signDetails.SignDesc, heightHint, nil, opts...,
	)
	input.blockToMaturity = 1

	return HtlcSecondLevelAnchorInput{
		inputKit:      input.inputKit,
		SignedTx:      signedTx,
		createWitness: createWitness,
	}
}

// MakeHtlcSecondLevelTimeoutTaprootInput creates an input that allows the
// sweeper to spend an HTLC output to the second level on our commitment
// transaction. The sweeper is also able to generate witnesses on demand to
// sweep the second level HTLC aggregated with other transactions.
func MakeHtlcSecondLevelTimeoutTaprootInput(signedTx *wire.MsgTx,
	signDetails *SignDetails,
	heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput {

	createWitness := func(signer Signer, txn *wire.MsgTx,
		hashCache *txscript.TxSigHashes,
		prevOutputFetcher txscript.PrevOutputFetcher,
		txinIdx int) (wire.TxWitness, error) {

		desc := signDetails.SignDesc
		if desc.ControlBlock == nil {
			return nil, fmt.Errorf("ctrl block must be set")
		}

		desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher)
		desc.InputIndex = txinIdx
		desc.PrevOutputFetcher = prevOutputFetcher

		desc.SignMethod = TaprootScriptSpendSignMethod

		return SenderHTLCScriptTaprootTimeout(
			signDetails.PeerSig, signDetails.SigHashType, signer,
			&desc, txn, nil, nil,
		)
	}

	input := MakeBaseInput(
		&signedTx.TxIn[0].PreviousOutPoint,
		TaprootHtlcLocalOfferedTimeout,
		&signDetails.SignDesc, heightHint, nil, opts...,
	)
	input.blockToMaturity = 1

	return HtlcSecondLevelAnchorInput{
		inputKit:      input.inputKit,
		SignedTx:      signedTx,
		createWitness: createWitness,
	}
}

// MakeHtlcSecondLevelSuccessAnchorInput creates an input allowing the sweeper
// to spend the HTLC output on our commit using the second level success
// transaction.
func MakeHtlcSecondLevelSuccessAnchorInput(signedTx *wire.MsgTx,
	signDetails *SignDetails, preimage lntypes.Preimage,
	heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput {

	// Spend an HTLC output on our local commitment tx using the 2nd
	// success transaction.
	createWitness := func(signer Signer, txn *wire.MsgTx,
		hashCache *txscript.TxSigHashes,
		prevOutputFetcher txscript.PrevOutputFetcher,
		txinIdx int) (wire.TxWitness, error) {

		desc := signDetails.SignDesc
		desc.SigHashes = hashCache
		desc.InputIndex = txinIdx
		desc.PrevOutputFetcher = prevOutputFetcher

		return ReceiverHtlcSpendRedeem(
			signDetails.PeerSig, signDetails.SigHashType,
			preimage[:], signer, &desc, txn,
		)
	}
	input := MakeBaseInput(
		&signedTx.TxIn[0].PreviousOutPoint,
		HtlcAcceptedSuccessSecondLevelInputConfirmed,
		&signDetails.SignDesc, heightHint, nil, opts...,
	)
	input.blockToMaturity = 1

	return HtlcSecondLevelAnchorInput{
		SignedTx:      signedTx,
		inputKit:      input.inputKit,
		createWitness: createWitness,
	}
}

// MakeHtlcSecondLevelSuccessTaprootInput creates an input that allows the
// sweeper to spend an HTLC output to the second level on our taproot
// commitment transaction.
func MakeHtlcSecondLevelSuccessTaprootInput(signedTx *wire.MsgTx,
	signDetails *SignDetails, preimage lntypes.Preimage,
	heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput {

	createWitness := func(signer Signer, txn *wire.MsgTx,
		hashCache *txscript.TxSigHashes,
		prevOutputFetcher txscript.PrevOutputFetcher,
		txinIdx int) (wire.TxWitness, error) {

		desc := signDetails.SignDesc
		if desc.ControlBlock == nil {
			return nil, fmt.Errorf("ctrl block must be set")
		}

		desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher)
		desc.InputIndex = txinIdx
		desc.PrevOutputFetcher = prevOutputFetcher

		desc.SignMethod = TaprootScriptSpendSignMethod

		return ReceiverHTLCScriptTaprootRedeem(
			signDetails.PeerSig, signDetails.SigHashType,
			preimage[:], signer, &desc, txn, nil, nil,
		)
	}

	input := MakeBaseInput(
		&signedTx.TxIn[0].PreviousOutPoint,
		TaprootHtlcAcceptedLocalSuccess,
		&signDetails.SignDesc, heightHint, nil, opts...,
	)
	input.blockToMaturity = 1

	return HtlcSecondLevelAnchorInput{
		inputKit:      input.inputKit,
		SignedTx:      signedTx,
		createWitness: createWitness,
	}
}

// Compile-time constraints to ensure each input struct implement the Input
// interface.
var _ Input = (*BaseInput)(nil)
var _ Input = (*HtlcSucceedInput)(nil)
var _ Input = (*HtlcSecondLevelAnchorInput)(nil)
