package netann

import (
	"bytes"
	"testing"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
	"github.com/btcsuite/btcd/btcutil"
	"github.com/btcsuite/btcd/chaincfg"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/wire"
	"github.com/lightningnetwork/lnd/graph/db/models"
	"github.com/lightningnetwork/lnd/input"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/lightningnetwork/lnd/tlv"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestCreateChanAnnouncement(t *testing.T) {
	t.Parallel()

	key := [33]byte{0x1}
	var sig lnwire.Sig
	features := lnwire.NewRawFeatureVector(lnwire.AnchorsRequired)
	var featuresBuf bytes.Buffer
	if err := features.Encode(&featuresBuf); err != nil {
		t.Fatalf("unable to encode features: %v", err)
	}

	expChanAnn := &lnwire.ChannelAnnouncement1{
		ChainHash:       chainhash.Hash{0x1},
		ShortChannelID:  lnwire.ShortChannelID{BlockHeight: 1},
		NodeID1:         key,
		NodeID2:         key,
		NodeSig1:        sig,
		NodeSig2:        sig,
		BitcoinKey1:     key,
		BitcoinKey2:     key,
		BitcoinSig1:     sig,
		BitcoinSig2:     sig,
		Features:        features,
		ExtraOpaqueData: []byte{0x1},
	}

	chanProof := &models.ChannelAuthProof{
		NodeSig1Bytes:    expChanAnn.NodeSig1.ToSignatureBytes(),
		NodeSig2Bytes:    expChanAnn.NodeSig2.ToSignatureBytes(),
		BitcoinSig1Bytes: expChanAnn.BitcoinSig1.ToSignatureBytes(),
		BitcoinSig2Bytes: expChanAnn.BitcoinSig2.ToSignatureBytes(),
	}
	chanInfo := &models.ChannelEdgeInfo{
		ChainHash:        expChanAnn.ChainHash,
		ChannelID:        expChanAnn.ShortChannelID.ToUint64(),
		ChannelPoint:     wire.OutPoint{Index: 1},
		Capacity:         btcutil.SatoshiPerBitcoin,
		NodeKey1Bytes:    key,
		NodeKey2Bytes:    key,
		BitcoinKey1Bytes: key,
		BitcoinKey2Bytes: key,
		Features:         featuresBuf.Bytes(),
		ExtraOpaqueData:  expChanAnn.ExtraOpaqueData,
	}
	chanAnn, _, _, err := CreateChanAnnouncement(
		chanProof, chanInfo, nil, nil,
	)
	require.NoError(t, err, "unable to create channel announcement")

	assert.Equal(t, chanAnn, expChanAnn)
}

// TestChanAnnounce2Validation checks that the various forms of the
// channel_announcement_2 message are validated correctly.
func TestChanAnnounce2Validation(t *testing.T) {
	t.Parallel()

	t.Run(
		"test 4-of-4 MuSig2 channel announcement",
		test4of4MuSig2ChanAnnouncement,
	)

	t.Run(
		"test 3-of-3 MuSig2 channel announcement",
		test3of3MuSig2ChanAnnouncement,
	)
}

// test4of4MuSig2ChanAnnouncement covers the case where both bitcoin keys are
// present in the channel announcement. In this case, the signature should be
// a 4-of-4 MuSig2.
func test4of4MuSig2ChanAnnouncement(t *testing.T) {
	t.Parallel()

	// Generate the keys for node 1 and node2.
	node1, node2 := genChanAnnKeys(t)

	// Build the unsigned channel announcement.
	ann := buildUnsignedChanAnnouncement(node1, node2, true)

	// Serialise the bytes that need to be signed.
	msg, err := ChanAnn2DigestToSign(ann)
	require.NoError(t, err)

	var msgBytes [32]byte
	copy(msgBytes[:], msg.CloneBytes())

	// Generate the 4 nonces required for producing the signature.
	var (
		node1NodeNonce = genNonceForPubKey(t, node1.nodePub)
		node1BtcNonce  = genNonceForPubKey(t, node1.btcPub)
		node2NodeNonce = genNonceForPubKey(t, node2.nodePub)
		node2BtcNonce  = genNonceForPubKey(t, node2.btcPub)
	)

	nonceAgg, err := musig2.AggregateNonces([][66]byte{
		node1NodeNonce.PubNonce,
		node1BtcNonce.PubNonce,
		node2NodeNonce.PubNonce,
		node2BtcNonce.PubNonce,
	})
	require.NoError(t, err)

	pubKeys := []*btcec.PublicKey{
		node1.nodePub, node2.nodePub, node1.btcPub, node2.btcPub,
	}

	// Let Node1 sign the announcement message with its node key.
	psA1, err := musig2.Sign(
		node1NodeNonce.SecNonce, node1.nodePriv, nonceAgg, pubKeys,
		msgBytes, musig2.WithSortedKeys(),
	)
	require.NoError(t, err)

	// Let Node1 sign the announcement message with its bitcoin key.
	psA2, err := musig2.Sign(
		node1BtcNonce.SecNonce, node1.btcPriv, nonceAgg, pubKeys,
		msgBytes, musig2.WithSortedKeys(),
	)
	require.NoError(t, err)

	// Let Node2 sign the announcement message with its node key.
	psB1, err := musig2.Sign(
		node2NodeNonce.SecNonce, node2.nodePriv, nonceAgg, pubKeys,
		msgBytes, musig2.WithSortedKeys(),
	)
	require.NoError(t, err)

	// Let Node2 sign the announcement message with its bitcoin key.
	psB2, err := musig2.Sign(
		node2BtcNonce.SecNonce, node2.btcPriv, nonceAgg, pubKeys,
		msgBytes, musig2.WithSortedKeys(),
	)
	require.NoError(t, err)

	// Finally, combine the partial signatures from Node1 and Node2 and add
	// the signature to the announcement message.
	s := musig2.CombineSigs(psA1.R, []*musig2.PartialSignature{
		psA1, psA2, psB1, psB2,
	})

	sig, err := lnwire.NewSigFromSignature(s)
	require.NoError(t, err)

	ann.Signature = sig

	// Validate the announcement.
	require.NoError(t, ValidateChannelAnn(ann, nil))
}

// test3of3MuSig2ChanAnnouncement covers the case where no bitcoin keys are
// present in the channel announcement. In this case, the signature should be
// a 3-of-3 MuSig2 where the keys making up the pub key are: node1 ID, node2 ID
// and the output key found on-chain in the funding transaction. As the
// verifier, we don't care about the construction of the output key. We only
// care that the channel peers were able to sign for the output key. In reality,
// this key will likely be constructed from at least 1 key from each peer and
// the partial signature for it will be constructed via nested MuSig2 but for
// the sake of the test, we will just have it be backed by a single key.
func test3of3MuSig2ChanAnnouncement(t *testing.T) {
	// Generate the keys for node 1 and node 2.
	node1, node2 := genChanAnnKeys(t)

	// Build the unsigned channel announcement.
	ann := buildUnsignedChanAnnouncement(node1, node2, false)

	// Serialise the bytes that need to be signed.
	msg, err := ChanAnn2DigestToSign(ann)
	require.NoError(t, err)

	var msgBytes [32]byte
	copy(msgBytes[:], msg.CloneBytes())

	// Create a random 3rd key to be used for the output key.
	outputKeyPriv, err := btcec.NewPrivateKey()
	require.NoError(t, err)

	outputKey := outputKeyPriv.PubKey()

	// Ensure that the output key has an even Y by negating the private key
	// if required.
	if outputKey.SerializeCompressed()[0] ==
		input.PubKeyFormatCompressedOdd {

		outputKeyPriv.Key.Negate()
		outputKey = outputKeyPriv.PubKey()
	}

	// Generate the nonces required for producing the partial signatures.
	var (
		node1NodeNonce = genNonceForPubKey(t, node1.nodePub)
		node2NodeNonce = genNonceForPubKey(t, node2.nodePub)
		outputKeyNonce = genNonceForPubKey(t, outputKey)
	)

	nonceAgg, err := musig2.AggregateNonces([][66]byte{
		node1NodeNonce.PubNonce,
		node2NodeNonce.PubNonce,
		outputKeyNonce.PubNonce,
	})
	require.NoError(t, err)

	pkScript, err := input.PayToTaprootScript(outputKey)
	require.NoError(t, err)

	// We'll pass in a mock tx fetcher that will return the funding output
	// containing this key. This is needed since the output key can not be
	// determined from the channel announcement itself.
	fetchTx := func(chanID *lnwire.ShortChannelID) ([]byte, error) {
		return pkScript, nil
	}

	pubKeys := []*btcec.PublicKey{node1.nodePub, node2.nodePub, outputKey}

	// Let Node1 sign the announcement message with its node key.
	psA, err := musig2.Sign(
		node1NodeNonce.SecNonce, node1.nodePriv, nonceAgg, pubKeys,
		msgBytes, musig2.WithSortedKeys(),
	)
	require.NoError(t, err)

	// Let Node2 sign the announcement message with its node key.
	psB, err := musig2.Sign(
		node2NodeNonce.SecNonce, node2.nodePriv, nonceAgg, pubKeys,
		msgBytes, musig2.WithSortedKeys(),
	)
	require.NoError(t, err)

	// Create a partial sig for the output key.
	psO, err := musig2.Sign(
		outputKeyNonce.SecNonce, outputKeyPriv, nonceAgg, pubKeys,
		msgBytes, musig2.WithSortedKeys(),
	)
	require.NoError(t, err)

	// Finally, combine the partial signatures from Node1 and Node2 and add
	// the signature to the announcement message.
	s := musig2.CombineSigs(psA.R, []*musig2.PartialSignature{
		psA, psB, psO,
	})

	sig, err := lnwire.NewSigFromSignature(s)
	require.NoError(t, err)

	ann.Signature = sig

	// Validate the announcement.
	require.NoError(t, ValidateChannelAnn(ann, fetchTx))
}

func genNonceForPubKey(t *testing.T, pub *btcec.PublicKey) *musig2.Nonces {
	nonce, err := musig2.GenNonces(musig2.WithPublicKey(pub))
	require.NoError(t, err)

	return nonce
}

type keyRing struct {
	nodePriv *btcec.PrivateKey
	nodePub  *btcec.PublicKey
	btcPriv  *btcec.PrivateKey
	btcPub   *btcec.PublicKey
}

func genChanAnnKeys(t *testing.T) (*keyRing, *keyRing) {
	// Let Alice and Bob derive the various keys they need.
	aliceNodePrivKey, err := btcec.NewPrivateKey()
	require.NoError(t, err)

	aliceNodeID := aliceNodePrivKey.PubKey()

	aliceBtcPrivKey, err := btcec.NewPrivateKey()
	require.NoError(t, err)

	bobNodePrivKey, err := btcec.NewPrivateKey()
	require.NoError(t, err)

	bobNodeID := bobNodePrivKey.PubKey()

	bobBtcPrivKey, err := btcec.NewPrivateKey()
	require.NoError(t, err)

	alice := &keyRing{
		nodePriv: aliceNodePrivKey,
		nodePub:  aliceNodePrivKey.PubKey(),
		btcPriv:  aliceBtcPrivKey,
		btcPub:   aliceBtcPrivKey.PubKey(),
	}

	bob := &keyRing{
		nodePriv: bobNodePrivKey,
		nodePub:  bobNodePrivKey.PubKey(),
		btcPriv:  bobBtcPrivKey,
		btcPub:   bobBtcPrivKey.PubKey(),
	}

	if bytes.Compare(
		aliceNodeID.SerializeCompressed(),
		bobNodeID.SerializeCompressed(),
	) != -1 {

		return bob, alice
	}

	return alice, bob
}

func buildUnsignedChanAnnouncement(node1, node2 *keyRing,
	withBtcKeys bool) *lnwire.ChannelAnnouncement2 {

	var ann lnwire.ChannelAnnouncement2
	ann.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash
	features := lnwire.NewRawFeatureVector()
	ann.Features.Val = *features
	ann.ShortChannelID.Val = lnwire.ShortChannelID{
		BlockHeight: 1000,
		TxIndex:     100,
		TxPosition:  0,
	}
	ann.Capacity.Val = 100000

	copy(ann.NodeID1.Val[:], node1.nodePub.SerializeCompressed())
	copy(ann.NodeID2.Val[:], node2.nodePub.SerializeCompressed())

	if !withBtcKeys {
		return &ann
	}

	btcKey1Bytes := tlv.ZeroRecordT[tlv.TlvType12, [33]byte]()
	btcKey2Bytes := tlv.ZeroRecordT[tlv.TlvType14, [33]byte]()

	copy(btcKey1Bytes.Val[:], node1.btcPub.SerializeCompressed())
	copy(btcKey2Bytes.Val[:], node2.btcPub.SerializeCompressed())

	ann.BitcoinKey1 = tlv.SomeRecordT(btcKey1Bytes)
	ann.BitcoinKey2 = tlv.SomeRecordT(btcKey2Bytes)

	return &ann
}
