package lnwallet

import (
	"encoding/hex"
	"sync/atomic"
	"time"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcutil"
	"github.com/btcsuite/btcd/btcutil/hdkeychain"
	"github.com/btcsuite/btcd/btcutil/psbt"
	"github.com/btcsuite/btcd/chaincfg"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcwallet/waddrmgr"
	base "github.com/btcsuite/btcwallet/wallet"
	"github.com/btcsuite/btcwallet/wallet/txauthor"
	"github.com/btcsuite/btcwallet/wtxmgr"
	"github.com/lightningnetwork/lnd/chainntnfs"
	"github.com/lightningnetwork/lnd/channeldb"
	"github.com/lightningnetwork/lnd/fn"
	"github.com/lightningnetwork/lnd/lnwallet/chainfee"
	"github.com/lightningnetwork/lnd/tlv"
	"github.com/stretchr/testify/mock"
)

var (
	CoinPkScript, _ = hex.DecodeString(
		"001431df1bde03c074d0cf21ea2529427e1499b8f1de",
	)
)

// mockWalletController is a mock implementation of the WalletController
// interface. It let's us mock the interaction with the bitcoin network.
type mockWalletController struct {
	RootKey               *btcec.PrivateKey
	PublishedTransactions chan *wire.MsgTx
	index                 uint32
	Utxos                 []*Utxo
}

// A compile time check to ensure that mockWalletController implements the
// WalletController.
var _ WalletController = (*mockWalletController)(nil)

// BackEnd returns "mock" to signify a mock wallet controller.
func (w *mockWalletController) BackEnd() string {
	return "mock"
}

// FetchOutpointInfo will be called to get info about the inputs to the funding
// transaction.
func (w *mockWalletController) FetchOutpointInfo(
	prevOut *wire.OutPoint) (*Utxo, error) {

	utxo := &Utxo{
		AddressType:   WitnessPubKey,
		Value:         10 * btcutil.SatoshiPerBitcoin,
		PkScript:      []byte("dummy"),
		Confirmations: 1,
		OutPoint:      *prevOut,
	}

	return utxo, nil
}

// ScriptForOutput returns the address, witness program and redeem script for a
// given UTXO. An error is returned if the UTXO does not belong to our wallet or
// it is not a managed pubKey address.
func (w *mockWalletController) ScriptForOutput(*wire.TxOut) (
	waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) {

	return nil, nil, nil, nil
}

// ConfirmedBalance currently returns dummy values.
func (w *mockWalletController) ConfirmedBalance(int32, string) (btcutil.Amount,
	error) {

	return 0, nil
}

// NewAddress is called to get new addresses for delivery, change etc.
func (w *mockWalletController) NewAddress(AddressType, bool,
	string) (btcutil.Address, error) {

	addr, _ := btcutil.NewAddressPubKey(
		w.RootKey.PubKey().SerializeCompressed(),
		&chaincfg.MainNetParams,
	)

	return addr, nil
}

// LastUnusedAddress currently returns dummy values.
func (w *mockWalletController) LastUnusedAddress(AddressType,
	string) (btcutil.Address, error) {

	return nil, nil
}

// IsOurAddress currently returns a dummy value.
func (w *mockWalletController) IsOurAddress(btcutil.Address) bool {
	return false
}

// AddressInfo currently returns a dummy value.
func (w *mockWalletController) AddressInfo(
	btcutil.Address) (waddrmgr.ManagedAddress, error) {

	return nil, nil
}

// ListAccounts currently returns a dummy value.
func (w *mockWalletController) ListAccounts(string,
	*waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) {

	return nil, nil
}

// RequiredReserve currently returns a dummy value.
func (w *mockWalletController) RequiredReserve(uint32) btcutil.Amount {
	return 0
}

// ListAddresses currently returns a dummy value.
func (w *mockWalletController) ListAddresses(string,
	bool) (AccountAddressMap, error) {

	return nil, nil
}

// ImportAccount currently returns a dummy value.
func (w *mockWalletController) ImportAccount(string, *hdkeychain.ExtendedKey,
	uint32, *waddrmgr.AddressType, bool) (*waddrmgr.AccountProperties,
	[]btcutil.Address, []btcutil.Address, error) {

	return nil, nil, nil, nil
}

// ImportPublicKey currently returns a dummy value.
func (w *mockWalletController) ImportPublicKey(*btcec.PublicKey,
	waddrmgr.AddressType) error {

	return nil
}

// ImportTaprootScript currently returns a dummy value.
func (w *mockWalletController) ImportTaprootScript(waddrmgr.KeyScope,
	*waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error) {

	return nil, nil
}

// SendOutputs currently returns dummy values.
func (w *mockWalletController) SendOutputs(fn.Set[wire.OutPoint], []*wire.TxOut,
	chainfee.SatPerKWeight, int32, string,
	base.CoinSelectionStrategy) (*wire.MsgTx, error) {

	return nil, nil
}

// CreateSimpleTx currently returns dummy values.
func (w *mockWalletController) CreateSimpleTx(fn.Set[wire.OutPoint],
	[]*wire.TxOut, chainfee.SatPerKWeight, int32,
	base.CoinSelectionStrategy, bool) (*txauthor.AuthoredTx, error) {

	return nil, nil
}

// ListUnspentWitness is called by the wallet when doing coin selection. We just
// need one unspent for the funding transaction.
func (w *mockWalletController) ListUnspentWitness(int32, int32,
	string) ([]*Utxo, error) {

	// If the mock already has a list of utxos, return it.
	if w.Utxos != nil {
		return w.Utxos, nil
	}

	// Otherwise create one to return.
	utxo := &Utxo{
		AddressType: WitnessPubKey,
		Value:       btcutil.Amount(10 * btcutil.SatoshiPerBitcoin),
		PkScript:    CoinPkScript,
		OutPoint: wire.OutPoint{
			Hash:  chainhash.Hash{},
			Index: w.index,
		},
	}
	atomic.AddUint32(&w.index, 1)
	var ret []*Utxo
	ret = append(ret, utxo)

	return ret, nil
}

// ListTransactionDetails currently returns dummy values.
func (w *mockWalletController) ListTransactionDetails(int32, int32,
	string) ([]*TransactionDetail, error) {

	return nil, nil
}

// LeaseOutput returns the current time and a nil error.
func (w *mockWalletController) LeaseOutput(wtxmgr.LockID, wire.OutPoint,
	time.Duration) (time.Time, error) {

	return time.Now(), nil
}

// ReleaseOutput currently does nothing.
func (w *mockWalletController) ReleaseOutput(wtxmgr.LockID,
	wire.OutPoint) error {

	return nil
}

func (w *mockWalletController) ListLeasedOutputs() (
	[]*base.ListLeasedOutputResult, error) {

	return nil, nil
}

// FundPsbt currently does nothing.
func (w *mockWalletController) FundPsbt(*psbt.Packet, int32,
	chainfee.SatPerKWeight, string, *waddrmgr.KeyScope,
	base.CoinSelectionStrategy, func(utxo wtxmgr.Credit) bool) (int32,
	error) {

	return 0, nil
}

// SignPsbt currently does nothing.
func (w *mockWalletController) SignPsbt(*psbt.Packet) ([]uint32, error) {
	return nil, nil
}

// FinalizePsbt currently does nothing.
func (w *mockWalletController) FinalizePsbt(_ *psbt.Packet, _ string) error {
	return nil
}

// DecorateInputs currently does nothing.
func (w *mockWalletController) DecorateInputs(*psbt.Packet, bool) error {
	return nil
}

// PublishTransaction sends a transaction to the PublishedTransactions chan.
func (w *mockWalletController) PublishTransaction(tx *wire.MsgTx,
	_ string) error {

	w.PublishedTransactions <- tx
	return nil
}

// GetTransactionDetails currently does nothing.
func (w *mockWalletController) GetTransactionDetails(*chainhash.Hash) (
	*TransactionDetail, error) {

	return nil, nil
}

// LabelTransaction currently does nothing.
func (w *mockWalletController) LabelTransaction(chainhash.Hash, string,
	bool) error {

	return nil
}

// SubscribeTransactions currently does nothing.
func (w *mockWalletController) SubscribeTransactions() (TransactionSubscription,
	error) {

	return nil, nil
}

// IsSynced currently returns dummy values.
func (w *mockWalletController) IsSynced() (bool, int64, error) {
	return true, int64(0), nil
}

// GetRecoveryInfo currently returns dummy values.
func (w *mockWalletController) GetRecoveryInfo() (bool, float64, error) {
	return true, float64(1), nil
}

// Start currently does nothing.
func (w *mockWalletController) Start() error {
	return nil
}

// Stop currently does nothing.
func (w *mockWalletController) Stop() error {
	return nil
}

func (w *mockWalletController) FetchTx(chainhash.Hash) (*wire.MsgTx, error) {
	return nil, nil
}

func (w *mockWalletController) RemoveDescendants(*wire.MsgTx) error {
	return nil
}

// FetchDerivationInfo queries for the wallet's knowledge of the passed
// pkScript and constructs the derivation info and returns it.
func (w *mockWalletController) FetchDerivationInfo(
	pkScript []byte) (*psbt.Bip32Derivation, error) {

	return nil, nil
}

func (w *mockWalletController) CheckMempoolAcceptance(tx *wire.MsgTx) error {
	return nil
}

// mockChainNotifier is a mock implementation of the ChainNotifier interface.
type mockChainNotifier struct {
	SpendChan chan *chainntnfs.SpendDetail
	EpochChan chan *chainntnfs.BlockEpoch
	ConfChan  chan *chainntnfs.TxConfirmation
}

// RegisterConfirmationsNtfn returns a ConfirmationEvent that contains a channel
// that the tx confirmation will go over.
func (c *mockChainNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
	pkScript []byte, numConfs, heightHint uint32,
	opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent,
	error) {

	return &chainntnfs.ConfirmationEvent{
		Confirmed: c.ConfChan,
		Cancel:    func() {},
	}, nil
}

// RegisterSpendNtfn returns a SpendEvent that contains a channel that the spend
// details will go over.
func (c *mockChainNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
	pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {

	return &chainntnfs.SpendEvent{
		Spend:  c.SpendChan,
		Cancel: func() {},
	}, nil
}

// RegisterBlockEpochNtfn returns a BlockEpochEvent that contains a channel that
// block epochs will go over.
func (c *mockChainNotifier) RegisterBlockEpochNtfn(
	blockEpoch *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent,
	error) {

	return &chainntnfs.BlockEpochEvent{
		Epochs: c.EpochChan,
		Cancel: func() {},
	}, nil
}

// Start currently returns a dummy value.
func (c *mockChainNotifier) Start() error {
	return nil
}

// Started currently returns a dummy value.
func (c *mockChainNotifier) Started() bool {
	return true
}

// Stop currently returns a dummy value.
func (c *mockChainNotifier) Stop() error {
	return nil
}

type mockChainIO struct{}

func (*mockChainIO) GetBestBlock() (*chainhash.Hash, int32, error) {
	return nil, 0, nil
}

func (*mockChainIO) GetUtxo(op *wire.OutPoint, _ []byte,
	heightHint uint32, _ <-chan struct{}) (*wire.TxOut, error) {

	return nil, nil
}

func (*mockChainIO) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
	return nil, nil
}

func (*mockChainIO) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock,
	error) {

	return nil, nil
}

func (*mockChainIO) GetBlockHeader(
	blockHash *chainhash.Hash) (*wire.BlockHeader, error) {

	return nil, nil
}

type MockAuxLeafStore struct{}

// A compile time check to ensure that MockAuxLeafStore implements the
// AuxLeafStore interface.
var _ AuxLeafStore = (*MockAuxLeafStore)(nil)

// FetchLeavesFromView attempts to fetch the auxiliary leaves that
// correspond to the passed aux blob, and pending original (unfiltered)
// HTLC view.
func (*MockAuxLeafStore) FetchLeavesFromView(
	_ CommitDiffAuxInput) fn.Result[CommitDiffAuxResult] {

	return fn.Ok(CommitDiffAuxResult{})
}

// FetchLeavesFromCommit attempts to fetch the auxiliary leaves that
// correspond to the passed aux blob, and an existing channel
// commitment.
func (*MockAuxLeafStore) FetchLeavesFromCommit(_ AuxChanState,
	_ channeldb.ChannelCommitment,
	_ CommitmentKeyRing) fn.Result[CommitDiffAuxResult] {

	return fn.Ok(CommitDiffAuxResult{})
}

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

	return fn.Ok(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.
func (*MockAuxLeafStore) ApplyHtlcView(
	_ CommitDiffAuxInput) fn.Result[fn.Option[tlv.Blob]] {

	return fn.Ok(fn.None[tlv.Blob]())
}

// EmptyMockJobHandler is a mock job handler that just sends an empty response
// to all jobs.
func EmptyMockJobHandler(jobs []AuxSigJob) {
	for _, sigJob := range jobs {
		sigJob.Resp <- AuxSigJobResp{}
	}
}

// MockAuxSigner is a mock implementation of the AuxSigner interface.
type MockAuxSigner struct {
	mock.Mock

	jobHandlerFunc func([]AuxSigJob)
}

// NewAuxSignerMock creates a new mock aux signer with the given job handler.
func NewAuxSignerMock(jobHandler func([]AuxSigJob)) *MockAuxSigner {
	return &MockAuxSigner{
		jobHandlerFunc: jobHandler,
	}
}

// SubmitSecondLevelSigBatch takes a batch of aux sign jobs and
// processes them asynchronously.
func (a *MockAuxSigner) SubmitSecondLevelSigBatch(chanState AuxChanState,
	tx *wire.MsgTx, jobs []AuxSigJob) error {

	args := a.Called(chanState, tx, jobs)

	if a.jobHandlerFunc != nil {
		a.jobHandlerFunc(jobs)
	}

	return args.Error(0)
}

// PackSigs takes a series of aux signatures and packs them into a
// single blob that can be sent alongside the CommitSig messages.
func (a *MockAuxSigner) PackSigs(
	sigs []fn.Option[tlv.Blob]) fn.Result[fn.Option[tlv.Blob]] {

	args := a.Called(sigs)

	return args.Get(0).(fn.Result[fn.Option[tlv.Blob]])
}

// UnpackSigs takes a packed blob of signatures and returns the
// original signatures for each HTLC, keyed by HTLC index.
func (a *MockAuxSigner) UnpackSigs(
	sigs fn.Option[tlv.Blob]) fn.Result[[]fn.Option[tlv.Blob]] {

	args := a.Called(sigs)

	return args.Get(0).(fn.Result[[]fn.Option[tlv.Blob]])
}

// VerifySecondLevelSigs attempts to synchronously verify a batch of aux
// sig jobs.
func (a *MockAuxSigner) VerifySecondLevelSigs(chanState AuxChanState,
	tx *wire.MsgTx, jobs []AuxVerifyJob) error {

	args := a.Called(chanState, tx, jobs)

	return args.Error(0)
}

type MockAuxContractResolver struct{}

// ResolveContract is called to resolve a contract that needs
// additional information to resolve properly. If no extra information
// is required, a nil Result error is returned.
func (*MockAuxContractResolver) ResolveContract(
	ResolutionReq) fn.Result[tlv.Blob] {

	return fn.Ok[tlv.Blob](nil)
}
