// Copyright (c) 2017 The btcsuite developers
// Copyright (c) 2017 The Lightning Network Developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package builder_test

import (
	"encoding/hex"
	"testing"

	"github.com/btcsuite/btcd/chaincfg"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
	"github.com/btcsuite/btcutil/gcs"
	"github.com/btcsuite/btcutil/gcs/builder"
)

var (
	// No need to allocate an err variable in every test
	err error

	// List of values for building a filter
	contents = [][]byte{
		[]byte("Alex"),
		[]byte("Bob"),
		[]byte("Charlie"),
		[]byte("Dick"),
		[]byte("Ed"),
		[]byte("Frank"),
		[]byte("George"),
		[]byte("Harry"),
		[]byte("Ilya"),
		[]byte("John"),
		[]byte("Kevin"),
		[]byte("Larry"),
		[]byte("Michael"),
		[]byte("Nate"),
		[]byte("Owen"),
		[]byte("Paul"),
		[]byte("Quentin"),
	}

	testKey = [16]byte{0x4c, 0xb1, 0xab, 0x12, 0x57, 0x62, 0x1e, 0x41,
		0x3b, 0x8b, 0x0e, 0x26, 0x64, 0x8d, 0x4a, 0x15}

	testHash = "000000000000000000496d7ff9bd2c96154a8d64260e8b3b411e625712abb14c"

	testAddr = "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v"

	witness = [][]byte{
		{0x4c, 0xb1, 0xab, 0x12, 0x57, 0x62, 0x1e, 0x41,
			0x3b, 0x8b, 0x0e, 0x26, 0x64, 0x8d, 0x4a, 0x15,
			0x3b, 0x8b, 0x0e, 0x26, 0x64, 0x8d, 0x4a, 0x15,
			0x3b, 0x8b, 0x0e, 0x26, 0x64, 0x8d, 0x4a, 0x15},

		{0xdd, 0xa3, 0x5a, 0x14, 0x88, 0xfb, 0x97, 0xb6,
			0xeb, 0x3f, 0xe6, 0xe9, 0xef, 0x2a, 0x25, 0x81,
			0x4e, 0x39, 0x6f, 0xb5, 0xdc, 0x29, 0x5f, 0xe9,
			0x94, 0xb9, 0x67, 0x89, 0xb2, 0x1a, 0x03, 0x98,
			0x94, 0xb9, 0x67, 0x89, 0xb2, 0x1a, 0x03, 0x98,
			0x94, 0xb9, 0x67, 0x89, 0xb2, 0x1a, 0x03, 0x98},
	}
)

// TestUseBlockHash tests using a block hash as a filter key.
func TestUseBlockHash(t *testing.T) {
	// Block hash #448710, pretty high difficulty.
	hash, err := chainhash.NewHashFromStr(testHash)
	if err != nil {
		t.Fatalf("Hash from string failed: %s", err.Error())
	}

	// wire.OutPoint
	outPoint := wire.OutPoint{
		Hash:  *hash,
		Index: 4321,
	}

	// btcutil.Address
	addr, err := btcutil.DecodeAddress(testAddr, &chaincfg.MainNetParams)
	if err != nil {
		t.Fatalf("Address decode failed: %s", err.Error())
	}
	addrBytes, err := txscript.PayToAddrScript(addr)
	if err != nil {
		t.Fatalf("Address script build failed: %s", err.Error())
	}

	// Create a GCSBuilder with a key hash and check that the key is derived
	// correctly, then test it.
	b := builder.WithKeyHash(hash)
	key, err := b.Key()
	if err != nil {
		t.Fatalf("Builder instantiation with key hash failed: %s",
			err.Error())
	}
	if key != testKey {
		t.Fatalf("Key not derived correctly from key hash:\n%s\n%s",
			hex.EncodeToString(key[:]),
			hex.EncodeToString(testKey[:]))
	}
	BuilderTest(b, hash, builder.DefaultP, outPoint, addrBytes, witness, t)

	// Create a GCSBuilder with a key hash and non-default P and test it.
	b = builder.WithKeyHashPM(hash, 30, 90)
	BuilderTest(b, hash, 30, outPoint, addrBytes, witness, t)

	// Create a GCSBuilder with a random key, set the key from a hash
	// manually, check that the key is correct, and test it.
	b = builder.WithRandomKey()
	b.SetKeyFromHash(hash)
	key, err = b.Key()
	if err != nil {
		t.Fatalf("Builder instantiation with known key failed: %s",
			err.Error())
	}
	if key != testKey {
		t.Fatalf("Key not copied correctly from known key:\n%s\n%s",
			hex.EncodeToString(key[:]),
			hex.EncodeToString(testKey[:]))
	}
	BuilderTest(b, hash, builder.DefaultP, outPoint, addrBytes, witness, t)

	// Create a GCSBuilder with a random key and test it.
	b = builder.WithRandomKey()
	key1, err := b.Key()
	if err != nil {
		t.Fatalf("Builder instantiation with random key failed: %s",
			err.Error())
	}
	t.Logf("Random Key 1: %s", hex.EncodeToString(key1[:]))
	BuilderTest(b, hash, builder.DefaultP, outPoint, addrBytes, witness, t)

	// Create a GCSBuilder with a random key and non-default P and test it.
	b = builder.WithRandomKeyPM(30, 90)
	key2, err := b.Key()
	if err != nil {
		t.Fatalf("Builder instantiation with random key failed: %s",
			err.Error())
	}
	t.Logf("Random Key 2: %s", hex.EncodeToString(key2[:]))
	if key2 == key1 {
		t.Fatalf("Random keys are the same!")
	}
	BuilderTest(b, hash, 30, outPoint, addrBytes, witness, t)

	// Create a GCSBuilder with a known key and test it.
	b = builder.WithKey(testKey)
	key, err = b.Key()
	if err != nil {
		t.Fatalf("Builder instantiation with known key failed: %s",
			err.Error())
	}
	if key != testKey {
		t.Fatalf("Key not copied correctly from known key:\n%s\n%s",
			hex.EncodeToString(key[:]),
			hex.EncodeToString(testKey[:]))
	}
	BuilderTest(b, hash, builder.DefaultP, outPoint, addrBytes, witness, t)

	// Create a GCSBuilder with a known key and non-default P and test it.
	b = builder.WithKeyPM(testKey, 30, 90)
	key, err = b.Key()
	if err != nil {
		t.Fatalf("Builder instantiation with known key failed: %s",
			err.Error())
	}
	if key != testKey {
		t.Fatalf("Key not copied correctly from known key:\n%s\n%s",
			hex.EncodeToString(key[:]),
			hex.EncodeToString(testKey[:]))
	}
	BuilderTest(b, hash, 30, outPoint, addrBytes, witness, t)

	// Create a GCSBuilder with a known key and too-high P and ensure error
	// works throughout all functions that use it.
	b = builder.WithRandomKeyPM(33, 99).SetKeyFromHash(hash).SetKey(testKey)
	b.SetP(30).AddEntry(hash.CloneBytes()).AddEntries(contents).
		AddHash(hash).AddEntry(addrBytes)
	_, err = b.Key()
	if err != gcs.ErrPTooBig {
		t.Fatalf("No error on P too big!")
	}
	_, err = b.Build()
	if err != gcs.ErrPTooBig {
		t.Fatalf("No error on P too big!")
	}
}

func BuilderTest(b *builder.GCSBuilder, hash *chainhash.Hash, p uint8,
	outPoint wire.OutPoint, addrBytes []byte, witness wire.TxWitness,
	t *testing.T) {

	key, err := b.Key()
	if err != nil {
		t.Fatalf("Builder instantiation with key hash failed: %s",
			err.Error())
	}

	// Build a filter and test matches.
	b.AddEntries(contents)
	f, err := b.Build()
	if err != nil {
		t.Fatalf("Filter build failed: %s", err.Error())
	}
	if f.P() != p {
		t.Fatalf("Filter built with wrong probability")
	}
	match, err := f.Match(key, []byte("Nate"))
	if err != nil {
		t.Fatalf("Filter match failed: %s", err)
	}
	if !match {
		t.Fatal("Filter didn't match when it should have!")
	}
	match, err = f.Match(key, []byte("weks"))
	if err != nil {
		t.Fatalf("Filter match failed: %s", err)
	}
	if match {
		t.Logf("False positive match, should be 1 in 2**%d!",
			builder.DefaultP)
	}

	// Add a hash, build a filter, and test matches
	b.AddHash(hash)
	f, err = b.Build()
	if err != nil {
		t.Fatalf("Filter build failed: %s", err.Error())
	}
	match, err = f.Match(key, hash.CloneBytes())
	if err != nil {
		t.Fatalf("Filter match failed: %s", err)
	}
	if !match {
		t.Fatal("Filter didn't match when it should have!")
	}

	// Add a script, build a filter, and test matches
	b.AddEntry(addrBytes)
	f, err = b.Build()
	if err != nil {
		t.Fatalf("Filter build failed: %s", err.Error())
	}
	match, err = f.MatchAny(key, [][]byte{addrBytes})
	if err != nil {
		t.Fatalf("Filter match any failed: %s", err)
	}
	if !match {
		t.Fatal("Filter didn't match when it should have!")
	}

	// Add a routine witness stack, build a filter, and test that it
	// matches.
	b.AddWitness(witness)
	f, err = b.Build()
	if err != nil {
		t.Fatalf("Filter build failed: %s", err.Error())
	}
	match, err = f.MatchAny(key, witness)
	if err != nil {
		t.Fatalf("Filter match any failed: %s", err)
	}
	if !match {
		t.Fatal("Filter didn't match when it should have!")
	}

	// Check that adding duplicate items does not increase filter size.
	originalSize := f.N()
	b.AddEntry(addrBytes)
	b.AddWitness(witness)
	f, err = b.Build()
	if err != nil {
		t.Fatalf("Filter build failed: %s", err.Error())
	}
	if f.N() != originalSize {
		t.Fatal("Filter size increased with duplicate items")
	}
}
