package itest

import (
	"context"
	"fmt"
	"testing"

	"github.com/btcsuite/btcutil"
	"github.com/btcsuite/btcwallet/waddrmgr"
	"github.com/lightningnetwork/lnd/keychain"
	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
	"github.com/lightningnetwork/lnd/lntest"
	"github.com/stretchr/testify/require"
)

var (
	rootKey = "tprv8ZgxMBicQKsPe6jS4vDm2n7s42Q6MpvghUQqMmSKG7bTZvGKtjrcU3" +
		"PGzMNG37yzxywrcdvgkwrr8eYXJmbwdvUNVT4Ucv7ris4jvA7BUmg"

	nodePubKey = "033f55d436d4f7d24aeffb1b976647380f22ebf9e74390e8c76dcff" +
		"9fea0093b7a"

	accounts = []*lnrpc.WatchOnlyAccount{{
		Purpose: waddrmgr.KeyScopeBIP0049Plus.Purpose,
		// We always use the mainnet coin type for our BIP49/84
		// addresses!
		CoinType: 0,
		Account:  0,
		Xpub: "tpubDDXEYWvGCTytEF6hBog9p4qr2QBUvJhh4P2wM4qHHv9N489khk" +
			"QoGkBXDVoquuiyBf8SKBwrYseYdtq9j2v2nttPpE8qbuW3sE2MCk" +
			"FPhTq",
	}, {
		Purpose: waddrmgr.KeyScopeBIP0084.Purpose,
		// We always use the mainnet coin type for our BIP49/84
		// addresses!
		CoinType: 0,
		Account:  0,
		Xpub: "tpubDDWAWrSLRSFrG1KdqXMQQyTKYGSKLKaY7gxpvK7RdV3e3Dkhvu" +
			"W2GgsFvsPN4RGmuoYtUgZ1LHZE8oftz7T4mzc1BxGt5rt8zJcVQi" +
			"KTPPV",
	}, {
		Purpose:  keychain.BIP0043Purpose,
		CoinType: harnessNetParams.HDCoinType,
		Account:  uint32(keychain.KeyFamilyMultiSig),
		Xpub: "tpubDDXFHr67Ro2tHKVWG2gNjjijKUH1Lyv5NKFYdJnuaLGVNBVwyV" +
			"5AbykhR43iy8wYozEMbw2QfmAqZhb8gnuL5mm9sZh8YsR6FjGAbe" +
			"w1xoT",
	}, {
		Purpose:  keychain.BIP0043Purpose,
		CoinType: harnessNetParams.HDCoinType,
		Account:  uint32(keychain.KeyFamilyRevocationBase),
		Xpub: "tpubDDXFHr67Ro2tKkccDqNfDqZpd5wCs2n6XRV2Uh185DzCTbkDaE" +
			"d9v7P837zZTYBNVfaRriuxgGVgxbGjDui4CKxyzBzwz4aAZxjn2P" +
			"hNcQy",
	}, {
		Purpose:  keychain.BIP0043Purpose,
		CoinType: harnessNetParams.HDCoinType,
		Account:  uint32(keychain.KeyFamilyHtlcBase),
		Xpub: "tpubDDXFHr67Ro2tNH4KH41i4oTsWfRjFigoH1Ee7urvHow51opH9x" +
			"J7mu1qSPMPVtkVqQZ5tE4NTuFJPrbDqno7TQietyUDmPTwyVviJb" +
			"GCwXk",
	}, {
		Purpose:  keychain.BIP0043Purpose,
		CoinType: harnessNetParams.HDCoinType,
		Account:  uint32(keychain.KeyFamilyPaymentBase),
		Xpub: "tpubDDXFHr67Ro2tQj5Zvav2ALhkU6dRQAhEtNPnYJVBC8hs2U1A9e" +
			"cqxRY3XTiJKBDD7e8tudhmTRs8aGWJAiAXJN5kXy3Hi6cmiwGWjX" +
			"K5Cv5",
	}, {
		Purpose:  keychain.BIP0043Purpose,
		CoinType: harnessNetParams.HDCoinType,
		Account:  uint32(keychain.KeyFamilyDelayBase),
		Xpub: "tpubDDXFHr67Ro2tSSR2LLBJtotxx2U45cuESLWKA72YT9td3SzVKH" +
			"AptzDEx5chsUNZ4WRMY5h6HJxRSebjRatxQKX1uUsux1LvKS1wsf" +
			"NJ2PH",
	}, {
		Purpose:  keychain.BIP0043Purpose,
		CoinType: harnessNetParams.HDCoinType,
		Account:  uint32(keychain.KeyFamilyRevocationRoot),
		Xpub: "tpubDDXFHr67Ro2tTwzfWvNoMoPpZbxdMEfe1WhbXJxvXikGixPa4g" +
			"gSRZeGx6T5yxVHTVT3rjVh35Veqsowj7emX8SZfXKDKDKcLduXCe" +
			"WPUU3",
	}, {
		Purpose:  keychain.BIP0043Purpose,
		CoinType: harnessNetParams.HDCoinType,
		Account:  uint32(keychain.KeyFamilyNodeKey),
		Xpub: "tpubDDXFHr67Ro2tYEDS2EByRedfsUoEwBtrzVbS1qdPrX6sAkUYGL" +
			"rZWvMmQv8KZDZ4zd9r8WzM9bJ2nGp7XuNVC4w2EBtWg7i76gbrmu" +
			"EWjQh",
	}, {
		Purpose:  keychain.BIP0043Purpose,
		CoinType: harnessNetParams.HDCoinType,
		Account:  uint32(keychain.KeyFamilyStaticBackup),
		Xpub: "tpubDDXFHr67Ro2tYpwnFJEQaM8eAPM2UV5uY6gFgXeSzS5aC5T9Tf" +
			"zXuawYKBbQMZJn8qHXLafY4tAutoda1aKP5h6Nbgy3swPbnhWbFj" +
			"S5wnX",
	}, {
		Purpose:  keychain.BIP0043Purpose,
		CoinType: harnessNetParams.HDCoinType,
		Account:  uint32(keychain.KeyFamilyTowerSession),
		Xpub: "tpubDDXFHr67Ro2tddKpAjUegXqt7EGxRXnHkeLbUkfuFMGbLJYgRp" +
			"G4ew5pMmGg2nmcGmHFQ29w3juNhd8N5ZZ8HwJdymC4f5ukQLJ4yg" +
			"9rEr3",
	}, {
		Purpose:  keychain.BIP0043Purpose,
		CoinType: harnessNetParams.HDCoinType,
		Account:  uint32(keychain.KeyFamilyTowerID),
		Xpub: "tpubDDXFHr67Ro2tgE89V8ZdgMytC2Jq1iT9ttGhdzR1X7haQJNBmX" +
			"t8kau6taC6DGASYzbrjmo9z9w6JQFcaLNqbhS2h2PVSzKf79j265" +
			"Zi8hF",
	}}
)

// testRemoteSigner tests that a watch-only wallet can use a remote signing
// wallet to perform any signing or ECDH operations.
func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) {
	ctxb := context.Background()

	subTests := []struct {
		name       string
		randomSeed bool
		sendCoins  bool
		fn         func(tt *harnessTest, wo, carol *lntest.HarnessNode)
	}{{
		name:       "random seed",
		randomSeed: true,
		fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
			// Nothing more to test here.
		},
	}, {
		name: "account import",
		fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
			runWalletImportAccountScenario(
				net, tt,
				walletrpc.AddressType_WITNESS_PUBKEY_HASH,
				carol, wo,
			)
		},
	}, {
		name:      "basic channel open close",
		sendCoins: true,
		fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
			runBasicChannelCreationAndUpdates(
				net, tt, wo, carol,
			)
		},
	}, {
		name:      "async payments",
		sendCoins: true,
		fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
			runAsyncPayments(net, tt, wo, carol)
		},
	}, {
		name: "shared key",
		fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
			runDeriveSharedKey(tt, wo)
		},
	}, {
		name:      "cpfp",
		sendCoins: true,
		fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
			runCPFP(net, tt, wo, carol)
		},
	}, {
		name: "psbt",
		fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
			runPsbtChanFunding(net, tt, carol, wo)
		},
	}}

	for _, st := range subTests {
		subTest := st

		// Signer is our signing node and has the wallet with the full
		// master private key. We test that we can create the watch-only
		// wallet from the exported accounts but also from a static key
		// to make sure the derivation of the account public keys is
		// correct in both cases.
		password := []byte("itestpassword")
		var (
			signerNodePubKey  = nodePubKey
			watchOnlyAccounts = accounts
			signer            *lntest.HarnessNode
			err               error
		)
		if !subTest.randomSeed {
			signer, err = net.RestoreNodeWithSeed(
				"Signer", nil, password, nil, rootKey, 0, nil,
			)
			require.NoError(t.t, err)
		} else {
			signer = net.NewNode(t.t, "Signer", nil)
			signerNodePubKey = signer.PubKeyStr

			rpcAccts, err := signer.WalletKitClient.ListAccounts(
				ctxb, &walletrpc.ListAccountsRequest{},
			)
			require.NoError(t.t, err)

			watchOnlyAccounts, err = walletrpc.AccountsToWatchOnly(
				rpcAccts.Accounts,
			)
			require.NoError(t.t, err)
		}

		// WatchOnly is the node that has a watch-only wallet and uses
		// the Signer node for any operation that requires access to
		// private keys.
		watchOnly, err := net.NewNodeRemoteSigner(
			"WatchOnly", []string{
				"--remotesigner.enable",
				fmt.Sprintf(
					"--remotesigner.rpchost=localhost:%d",
					signer.Cfg.RPCPort,
				),
				fmt.Sprintf(
					"--remotesigner.tlscertpath=%s",
					signer.Cfg.TLSCertPath,
				),
				fmt.Sprintf(
					"--remotesigner.macaroonpath=%s",
					signer.Cfg.AdminMacPath,
				),
			}, password, &lnrpc.WatchOnly{
				MasterKeyBirthdayTimestamp: 0,
				MasterKeyFingerprint:       nil,
				Accounts:                   watchOnlyAccounts,
			},
		)
		require.NoError(t.t, err)

		resp, err := watchOnly.GetInfo(ctxb, &lnrpc.GetInfoRequest{})
		require.NoError(t.t, err)

		require.Equal(t.t, signerNodePubKey, resp.IdentityPubkey)

		if subTest.sendCoins {
			net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, watchOnly)
			assertAccountBalance(
				t.t, signer, "default",
				btcutil.SatoshiPerBitcoin, 0,
			)
			assertAccountBalance(
				t.t, watchOnly, "default",
				btcutil.SatoshiPerBitcoin, 0,
			)
		}

		carol := net.NewNode(t.t, "carol", nil)
		net.EnsureConnected(t.t, watchOnly, carol)

		success := t.t.Run(subTest.name, func(tt *testing.T) {
			ht := newHarnessTest(tt, net)
			subTest.fn(ht, watchOnly, carol)
		})

		shutdownAndAssert(net, t, carol)
		shutdownAndAssert(net, t, watchOnly)
		shutdownAndAssert(net, t, signer)

		if !success {
			return
		}
	}
}
