package lnwire

import (
	"bytes"
	"math"
	"reflect"
	"sort"
	"testing"

	"github.com/stretchr/testify/require"
)

var testFeatureNames = map[FeatureBit]string{
	0: "feature1",
	3: "feature2",
	4: "feature3",
	5: "feature3",
}

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

	tests := []struct {
		bits             []FeatureBit
		expectedFeatures []bool
	}{
		// No features are enabled if no bits are set.
		{
			bits:             nil,
			expectedFeatures: []bool{false, false, false, false, false, false, false, false},
		},
		// Test setting an even bit for an even-only bit feature. The
		// corresponding odd bit should not be seen as set.
		{
			bits:             []FeatureBit{0},
			expectedFeatures: []bool{true, false, false, false, false, false, false, false},
		},
		// Test setting an odd bit for an even-only bit feature. The
		// corresponding even bit should not be seen as set.
		{
			bits:             []FeatureBit{1},
			expectedFeatures: []bool{false, true, false, false, false, false, false, false},
		},
		// Test setting an even bit for an odd-only bit feature. The bit should
		// be seen as set and the odd bit should not.
		{
			bits:             []FeatureBit{2},
			expectedFeatures: []bool{false, false, true, false, false, false, false, false},
		},
		// Test setting an odd bit for an odd-only bit feature. The bit should
		// be seen as set and the even bit should not.
		{
			bits:             []FeatureBit{3},
			expectedFeatures: []bool{false, false, false, true, false, false, false, false},
		},
		// Test setting an even bit for even-odd pair feature. Both bits in the
		// pair should be seen as set.
		{
			bits:             []FeatureBit{4},
			expectedFeatures: []bool{false, false, false, false, true, true, false, false},
		},
		// Test setting an odd bit for even-odd pair feature. Both bits in the
		// pair should be seen as set.
		{
			bits:             []FeatureBit{5},
			expectedFeatures: []bool{false, false, false, false, true, true, false, false},
		},
		// Test setting an even bit for an unknown feature. The bit should be
		// seen as set and the odd bit should not.
		{
			bits:             []FeatureBit{6},
			expectedFeatures: []bool{false, false, false, false, false, false, true, false},
		},
		// Test setting an odd bit for an unknown feature. The bit should be
		// seen as set and the odd bit should not.
		{
			bits:             []FeatureBit{7},
			expectedFeatures: []bool{false, false, false, false, false, false, false, true},
		},
	}

	fv := NewFeatureVector(nil, testFeatureNames)
	for i, test := range tests {
		for _, bit := range test.bits {
			fv.Set(bit)
		}

		for j, expectedSet := range test.expectedFeatures {
			if fv.HasFeature(FeatureBit(j)) != expectedSet {
				t.Errorf("Expectation failed in case %d, bit %d", i, j)
				break
			}
		}

		for _, bit := range test.bits {
			fv.Unset(bit)
		}
	}
}

// TestFeatureVectorRequiresFeature tests that if a feature vector only
// includes a required feature bit (it's even), then the RequiresFeature method
// will return true for both that bit as well as it's optional counter party.
func TestFeatureVectorRequiresFeature(t *testing.T) {
	t.Parallel()

	// Create a new feature vector with the features above, and set only
	// the set of required bits. These will be all the even features
	// referenced above.
	fv := NewFeatureVector(nil, testFeatureNames)
	fv.Set(0)
	fv.Set(4)

	// Next we'll query for those exact bits, these should show up as being
	// required.
	require.True(t, fv.RequiresFeature(0))
	require.True(t, fv.RequiresFeature(4))

	// If we query for the odd (optional) counter party to each of the
	// features, the method should still return that the backing feature
	// vector requires the feature to be set.
	require.True(t, fv.RequiresFeature(1))
	require.True(t, fv.RequiresFeature(5))
}

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

	tests := []struct {
		bits            []FeatureBit
		expectedEncoded []byte
	}{
		{
			bits:            nil,
			expectedEncoded: []byte{0x00, 0x00},
		},
		{
			bits:            []FeatureBit{2, 3, 7},
			expectedEncoded: []byte{0x00, 0x01, 0x8C},
		},
		{
			bits:            []FeatureBit{2, 3, 8},
			expectedEncoded: []byte{0x00, 0x02, 0x01, 0x0C},
		},
	}

	for i, test := range tests {
		fv := NewRawFeatureVector(test.bits...)

		// Test that Encode produces the correct serialization.
		buffer := new(bytes.Buffer)
		err := fv.Encode(buffer)
		if err != nil {
			t.Errorf("Failed to encode feature vector in case %d: %v", i, err)
			continue
		}

		encoded := buffer.Bytes()
		if !bytes.Equal(encoded, test.expectedEncoded) {
			t.Errorf("Wrong encoding in case %d: got %v, expected %v",
				i, encoded, test.expectedEncoded)
			continue
		}

		// Test that decoding then re-encoding produces the same result.
		fv2 := NewRawFeatureVector()
		err = fv2.Decode(bytes.NewReader(encoded))
		if err != nil {
			t.Errorf("Failed to decode feature vector in case %d: %v", i, err)
			continue
		}

		buffer2 := new(bytes.Buffer)
		err = fv2.Encode(buffer2)
		if err != nil {
			t.Errorf("Failed to re-encode feature vector in case %d: %v",
				i, err)
			continue
		}

		reencoded := buffer2.Bytes()
		if !bytes.Equal(reencoded, test.expectedEncoded) {
			t.Errorf("Wrong re-encoding in case %d: got %v, expected %v",
				i, reencoded, test.expectedEncoded)
		}
	}
}

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

	tests := []struct {
		bits            []FeatureBit
		expectedUnknown []FeatureBit
	}{
		{
			bits:            nil,
			expectedUnknown: nil,
		},
		// Since bits {0, 3, 4, 5} are known, and only even bits are considered
		// required (according to the "it's OK to be odd rule"), that leaves
		// {2, 6} as both unknown and required.
		{
			bits:            []FeatureBit{0, 1, 2, 3, 4, 5, 6, 7},
			expectedUnknown: []FeatureBit{2, 6},
		},
	}

	for i, test := range tests {
		rawVector := NewRawFeatureVector(test.bits...)
		fv := NewFeatureVector(rawVector, testFeatureNames)

		unknown := fv.UnknownRequiredFeatures()

		// Sort to make comparison independent of order
		sort.Slice(unknown, func(i, j int) bool {
			return unknown[i] < unknown[j]
		})
		if !reflect.DeepEqual(unknown, test.expectedUnknown) {
			t.Errorf("Wrong unknown features in case %d: got %v, expected %v",
				i, unknown, test.expectedUnknown)
		}
	}
}

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

	tests := []struct {
		bit           FeatureBit
		expectedName  string
		expectedKnown bool
	}{
		{
			bit:           0,
			expectedName:  "feature1",
			expectedKnown: true,
		},
		{
			bit:           1,
			expectedName:  "unknown",
			expectedKnown: false,
		},
		{
			bit:           2,
			expectedName:  "unknown",
			expectedKnown: false,
		},
		{
			bit:           3,
			expectedName:  "feature2",
			expectedKnown: true,
		},
		{
			bit:           4,
			expectedName:  "feature3",
			expectedKnown: true,
		},
		{
			bit:           5,
			expectedName:  "feature3",
			expectedKnown: true,
		},
		{
			bit:           6,
			expectedName:  "unknown",
			expectedKnown: false,
		},
		{
			bit:           7,
			expectedName:  "unknown",
			expectedKnown: false,
		},
	}

	fv := NewFeatureVector(nil, testFeatureNames)
	for _, test := range tests {
		name := fv.Name(test.bit)
		if name != test.expectedName {
			t.Errorf("Name for feature bit %d is incorrect: "+
				"expected %s, got %s", test.bit, name, test.expectedName)
		}

		known := fv.IsKnown(test.bit)
		if known != test.expectedKnown {
			t.Errorf("IsKnown for feature bit %d is incorrect: "+
				"expected %v, got %v", test.bit, known, test.expectedKnown)
		}
	}
}

// TestIsRequired asserts that feature bits properly return their IsRequired
// status. We require that even features be required and odd features be
// optional.
func TestIsRequired(t *testing.T) {
	optional := FeatureBit(1)
	if optional.IsRequired() {
		t.Fatalf("optional feature should not be required")
	}

	required := FeatureBit(0)
	if !required.IsRequired() {
		t.Fatalf("required feature should be required")
	}
}

// TestFeatures asserts that the Features() method on a FeatureVector properly
// returns the set of feature bits it stores internally.
func TestFeatures(t *testing.T) {
	tests := []struct {
		name string
		exp  map[FeatureBit]struct{}
	}{
		{
			name: "empty",
			exp:  map[FeatureBit]struct{}{},
		},
		{
			name: "one",
			exp: map[FeatureBit]struct{}{
				5: {},
			},
		},
		{
			name: "several",
			exp: map[FeatureBit]struct{}{
				0:     {},
				5:     {},
				23948: {},
			},
		},
	}

	toRawFV := func(set map[FeatureBit]struct{}) *RawFeatureVector {
		var bits []FeatureBit
		for bit := range set {
			bits = append(bits, bit)
		}
		return NewRawFeatureVector(bits...)
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			fv := NewFeatureVector(
				toRawFV(test.exp), Features,
			)

			if !reflect.DeepEqual(fv.Features(), test.exp) {
				t.Fatalf("feature mismatch, want: %v, got: %v",
					test.exp, fv.Features())
			}
		})
	}
}

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

	features := []FeatureBit{
		StaticRemoteKeyOptional,
		AnchorsZeroFeeHtlcTxOptional,
		ExplicitChannelTypeRequired,
	}
	fv := NewRawFeatureVector(features...)
	require.True(t, fv.OnlyContains(features...))
	require.False(t, fv.OnlyContains(features[:1]...))
}

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

	a := NewRawFeatureVector(
		StaticRemoteKeyOptional,
		AnchorsZeroFeeHtlcTxOptional,
		ExplicitChannelTypeRequired,
	)
	b := a.Clone()
	require.True(t, a.Equals(b))

	b.Unset(ExplicitChannelTypeRequired)
	require.False(t, a.Equals(b))

	b.Set(ExplicitChannelTypeOptional)
	require.False(t, a.Equals(b))
}

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

	fv := NewRawFeatureVector()
	require.True(t, fv.IsEmpty())

	fv.Set(StaticRemoteKeyOptional)
	require.False(t, fv.IsEmpty())

	fv.Unset(StaticRemoteKeyOptional)
	require.True(t, fv.IsEmpty())
}

// TestValidatePairs tests that feature vectors can only set the required or
// optional feature bit in a pair, not both.
func TestValidatePairs(t *testing.T) {
	t.Parallel()

	rfv := NewRawFeatureVector(
		StaticRemoteKeyOptional,
		StaticRemoteKeyRequired,
	)
	require.Equal(t, ErrFeaturePairExists, rfv.ValidatePairs())

	rfv = NewRawFeatureVector(
		StaticRemoteKeyOptional,
		PaymentAddrRequired,
	)
	require.Nil(t, rfv.ValidatePairs())
}

// TestValidateUpdate tests validation of an update to a feature vector.
func TestValidateUpdate(t *testing.T) {
	t.Parallel()

	testCases := []struct {
		name            string
		currentFeatures []FeatureBit
		newFeatures     []FeatureBit
		maximumValue    FeatureBit
		err             error
	}{
		{
			name: "defined feature bit set, can include",
			currentFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
			},
			newFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
			},
			err: nil,
		},
		{
			name: "defined feature bit not already set",
			currentFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
			},
			newFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
				PaymentAddrRequired,
			},
			err: ErrFeatureStandard,
		},
		{
			name: "known feature missing",
			currentFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
				PaymentAddrRequired,
			},
			newFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
			},
			err: ErrFeatureStandard,
		},
		{
			name: "can set unknown feature",
			currentFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
			},
			newFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
				FeatureBit(1001),
			},
			err: nil,
		},
		{
			name: "can unset unknown feature",
			currentFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
				FeatureBit(1001),
			},
			newFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
			},
			err: nil,
		},
		{
			name: "at allowed maximum",
			currentFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
			},
			newFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
				100,
			},
			maximumValue: 100,
			err:          nil,
		},
		{
			name: "above allowed maximum",
			currentFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
			},
			newFeatures: []FeatureBit{
				StaticRemoteKeyOptional,
				101,
			},
			maximumValue: 100,
			err:          ErrFeatureBitMaximum,
		},
	}

	for _, testCase := range testCases {
		testCase := testCase

		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()
			currentFV := NewRawFeatureVector(
				testCase.currentFeatures...,
			)
			newFV := NewRawFeatureVector(testCase.newFeatures...)

			// Set maximum value if not populated in the test case.
			maximumValue := testCase.maximumValue
			if testCase.maximumValue == 0 {
				maximumValue = math.MaxUint16
			}

			err := currentFV.ValidateUpdate(newFV, maximumValue)
			require.ErrorIs(t, err, testCase.err)
		})
	}
}
