package walletunlocker_test

import (
	"bytes"
	"io/ioutil"
	"os"
	"testing"
	"time"

	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
	"github.com/lightningnetwork/lnd/walletunlocker"
	"github.com/roasbeef/btcd/chaincfg"
	"github.com/roasbeef/btcwallet/wallet"
	"golang.org/x/net/context"
)

const (
	walletDbName = "wallet.db"
)

var (
	testPassword = []byte("test-password")
	testSeed     = []byte("test-seed-123456789")

	testNetParams = &chaincfg.MainNetParams
)

func createTestWallet(t *testing.T, dir string, netParams *chaincfg.Params) {
	netDir := btcwallet.NetworkDir(dir, netParams)
	loader := wallet.NewLoader(netParams, netDir)
	_, err := loader.CreateNewWallet(testPassword, testPassword, testSeed)
	if err != nil {
		t.Fatalf("failed creating wallet: %v", err)
	}
	err = loader.UnloadWallet()
	if err != nil {
		t.Fatalf("failed unloading wallet: %v", err)
	}
}

// TestCreateWallet checks that CreateWallet correctly returns a password that
// can be used for creating a wallet if no wallet exists from before, and
// returns an error when it already exists.
func TestCreateWallet(t *testing.T) {
	t.Parallel()

	// testDir is empty, meaning wallet was not created from before.
	testDir, err := ioutil.TempDir("", "testcreate")
	if err != nil {
		t.Fatalf("unable to create temp directory: %v", err)
	}
	defer func() {
		os.RemoveAll(testDir)
	}()

	// Create new UnlockerService.
	service := walletunlocker.New(nil, testDir, testNetParams)

	ctx := context.Background()
	req := &lnrpc.CreateWalletRequest{
		Password: testPassword,
	}
	_, err = service.CreateWallet(ctx, req)
	if err != nil {
		t.Fatalf("CreateWallet call failed: %v", err)
	}

	// Password should be sent over the channel.
	select {
	case pw := <-service.CreatePasswords:
		if !bytes.Equal(pw, testPassword) {
			t.Fatalf("expected to receive password %x, got %x",
				testPassword, pw)
		}
	case <-time.After(3 * time.Second):
		t.Fatalf("password not received")
	}

	// Create a wallet in testDir.
	createTestWallet(t, testDir, testNetParams)

	// Now calling CreateWallet should fail, since a wallet already exists
	// in the directory.
	_, err = service.CreateWallet(ctx, req)
	if err == nil {
		t.Fatalf("CreateWallet did not fail as expected")
	}
}

// TestUnlockWallet checks that trying to unlock non-existing wallet fail, that
// unlocking existing wallet with wrong passphrase fails, and that unlocking
// existing wallet with correct passphrase succeeds.
func TestUnlockWallet(t *testing.T) {
	t.Parallel()

	// testDir is empty, meaning wallet was not created from before.
	testDir, err := ioutil.TempDir("", "testunlock")
	if err != nil {
		t.Fatalf("unable to create temp directory: %v", err)
	}
	defer func() {
		os.RemoveAll(testDir)
	}()

	// Create new UnlockerService.
	service := walletunlocker.New(nil, testDir, testNetParams)

	ctx := context.Background()
	req := &lnrpc.UnlockWalletRequest{
		Password: testPassword,
	}

	// Should fail to unlock non-existing wallet.
	_, err = service.UnlockWallet(ctx, req)
	if err == nil {
		t.Fatalf("expected call to UnlockWallet to fail")
	}

	// Create a wallet we can try to unlock.
	createTestWallet(t, testDir, testNetParams)

	// Try unlocking this wallet with the wrong passphrase.
	wrongReq := &lnrpc.UnlockWalletRequest{
		Password: []byte("wrong-ofc"),
	}
	_, err = service.UnlockWallet(ctx, wrongReq)
	if err == nil {
		t.Fatalf("expected call to UnlockWallet to fail")
	}

	// With the correct password, we should be able to unlock the wallet.
	_, err = service.UnlockWallet(ctx, req)
	if err != nil {
		t.Fatalf("unable to unlock wallet: %v", err)
	}

	// Password should be sent over the channel.
	select {
	case pw := <-service.UnlockPasswords:
		if !bytes.Equal(pw, testPassword) {
			t.Fatalf("expected to receive password %x, got %x",
				testPassword, pw)
		}
	case <-time.After(3 * time.Second):
		t.Fatalf("password not received")
	}

}
