package models

import (
	"testing"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
	"github.com/btcsuite/btcd/chainhash/v2"
	"github.com/lightningnetwork/lnd/fn/v2"
	"github.com/lightningnetwork/lnd/input"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/lightningnetwork/lnd/routing/route"
	"github.com/stretchr/testify/require"
)

// TestFundingPKScriptV2 tests the FundingPKScript method for v2 channels
// which uses MuSig2 key aggregation.
func TestFundingPKScriptV2(t *testing.T) {
	t.Parallel()

	// Generate two test keys for bitcoin keys.
	privKey1, err := btcec.NewPrivateKey()
	require.NoError(t, err)
	pubKey1 := privKey1.PubKey()

	privKey2, err := btcec.NewPrivateKey()
	require.NoError(t, err)
	pubKey2 := privKey2.PubKey()

	// Convert to route.Vertex format.
	var btcKey1, btcKey2 route.Vertex
	copy(btcKey1[:], pubKey1.SerializeCompressed())
	copy(btcKey2[:], pubKey2.SerializeCompressed())

	// Create a test merkle root.
	var merkleRoot chainhash.Hash
	copy(merkleRoot[:], []byte("test-merkle-root-hash-32-bytes!"))

	t.Run("v2 with btc keys, no merkle root (BIP86)", func(t *testing.T) {
		t.Parallel()

		edge := &ChannelEdgeInfo{
			Version:          lnwire.GossipVersion2,
			BitcoinKey1Bytes: fn.Some(btcKey1),
			BitcoinKey2Bytes: fn.Some(btcKey2),
		}

		pkScript, err := edge.FundingPKScript()
		require.NoError(t, err)
		require.NotEmpty(t, pkScript)

		// Verify it's a valid taproot script (OP_1 <32-byte-key>).
		require.Len(t, pkScript, 34)
		require.Equal(t, byte(0x51), pkScript[0]) // OP_1

		// Manually compute expected script using BIP86 tweak.
		combinedKey, _, _, err := musig2.AggregateKeys(
			[]*btcec.PublicKey{pubKey1, pubKey2}, true,
			musig2.WithBIP86KeyTweak(),
		)
		require.NoError(t, err)

		expectedScript, err := input.PayToTaprootScript(
			combinedKey.FinalKey,
		)
		require.NoError(t, err)
		require.Equal(t, expectedScript, pkScript)
	})

	t.Run("v2 with bitcoin keys and merkle root", func(t *testing.T) {
		t.Parallel()

		edge := &ChannelEdgeInfo{
			Version:          lnwire.GossipVersion2,
			BitcoinKey1Bytes: fn.Some(btcKey1),
			BitcoinKey2Bytes: fn.Some(btcKey2),
			MerkleRootHash:   fn.Some(merkleRoot),
		}

		pkScript, err := edge.FundingPKScript()
		require.NoError(t, err)
		require.NotEmpty(t, pkScript)

		// Verify it's a valid taproot script.
		require.Len(t, pkScript, 34)
		require.Equal(t, byte(0x51), pkScript[0]) // OP_1

		// Manually compute expected script with taproot tweak.
		combinedKey, _, _, err := musig2.AggregateKeys(
			[]*btcec.PublicKey{pubKey1, pubKey2}, true,
			musig2.WithTaprootKeyTweak(merkleRoot[:]),
		)
		require.NoError(t, err)

		expectedScript, err := input.PayToTaprootScript(
			combinedKey.FinalKey,
		)
		require.NoError(t, err)
		require.Equal(t, expectedScript, pkScript)
	})

	t.Run("v2 no btc keys returns stored script", func(t *testing.T) {
		t.Parallel()

		storedScript := []byte{0x51, 0x20} // OP_1 + push 32
		storedScript = append(storedScript, make([]byte, 32)...)

		edge := &ChannelEdgeInfo{
			Version:       lnwire.GossipVersion2,
			FundingScript: fn.Some(storedScript),
		}

		pkScript, err := edge.FundingPKScript()
		require.NoError(t, err)
		require.Equal(t, storedScript, pkScript)
	})

	t.Run("v2 no btc keys and no stored script errors", func(t *testing.T) {
		t.Parallel()

		edge := &ChannelEdgeInfo{
			Version: lnwire.GossipVersion2,
		}

		_, err := edge.FundingPKScript()
		require.Error(t, err)
		require.Contains(t, err.Error(), "expected a funding pk script")
	})

	t.Run("v2 one btc key returns stored script", func(t *testing.T) {
		t.Parallel()

		storedScript := []byte{0x51, 0x20}
		storedScript = append(storedScript, make([]byte, 32)...)

		// Only key1 set, key2 missing.
		edge := &ChannelEdgeInfo{
			Version:          lnwire.GossipVersion2,
			BitcoinKey1Bytes: fn.Some(btcKey1),
			FundingScript:    fn.Some(storedScript),
		}

		pkScript, err := edge.FundingPKScript()
		require.NoError(t, err)
		require.Equal(t, storedScript, pkScript)
	})
}

// TestFundingPKScriptV1TaprootFeatureBit tests that a v1 channel edge carrying
// the taproot staging bit reconstructs a taproot funding script.
func TestFundingPKScriptV1TaprootFeatureBit(t *testing.T) {
	t.Parallel()

	privKey1, err := btcec.NewPrivateKey()
	require.NoError(t, err)
	pubKey1 := privKey1.PubKey()

	privKey2, err := btcec.NewPrivateKey()
	require.NoError(t, err)
	pubKey2 := privKey2.PubKey()

	var btcKey1, btcKey2 route.Vertex
	copy(btcKey1[:], pubKey1.SerializeCompressed())
	copy(btcKey2[:], pubKey2.SerializeCompressed())

	// Build a v1 edge that only has the legacy bitcoin keys populated, but
	// does advertise the taproot staging bit in its feature vector.
	edge := &ChannelEdgeInfo{
		Version:          lnwire.GossipVersion1,
		BitcoinKey1Bytes: fn.Some(btcKey1),
		BitcoinKey2Bytes: fn.Some(btcKey2),
		Features: lnwire.NewFeatureVector(
			lnwire.NewRawFeatureVector(
				lnwire.SimpleTaprootChannelsRequiredStaging,
			),
			lnwire.Features,
		),
	}

	pkScript, err := edge.FundingPKScript()
	require.NoError(t, err)

	// The fix should make FundingPKScript honor the feature bit and derive
	// the taproot funding script directly from the stored bitcoin keys.
	expectedScript, _, err := input.GenTaprootFundingScript(
		pubKey1, pubKey2, 0, fn.None[chainhash.Hash](),
	)
	require.NoError(t, err)

	require.Equal(t, expectedScript, pkScript)
}
