package chanbackup

import (
	"bytes"
	"net"
	"testing"

	"github.com/lightningnetwork/lnd/lnencrypt"
	"github.com/stretchr/testify/require"
)

// TestMultiPackUnpack...
func TestMultiPackUnpack(t *testing.T) {
	t.Parallel()

	var multi Multi
	numSingles := 10
	originalSingles := make([]Single, 0, numSingles)
	for i := 0; i < numSingles; i++ {
		channel, err := genRandomOpenChannelShell()
		if err != nil {
			t.Fatalf("unable to gen channel: %v", err)
		}

		single := NewSingle(channel, []net.Addr{addr1, addr2})

		originalSingles = append(originalSingles, single)
		multi.StaticBackups = append(multi.StaticBackups, single)
	}

	keyRing := &lnencrypt.MockKeyRing{}

	versionTestCases := []struct {
		// version is the pack/unpack version that we should use to
		// decode/encode the final SCB.
		version MultiBackupVersion

		// valid tests us if this test case should pass or not.
		valid bool
	}{
		// The default version, should pack/unpack with no problem.
		{
			version: DefaultSingleVersion,
			valid:   true,
		},

		// A non-default version, atm this should result in a failure.
		{
			version: 99,
			valid:   false,
		},
	}
	for i, versionCase := range versionTestCases {
		multi.Version = versionCase.version

		var b bytes.Buffer
		err := multi.PackToWriter(&b, keyRing)
		switch {
		// If this is a valid test case, and we failed, then we'll
		// return an error.
		case err != nil && versionCase.valid:
			t.Fatalf("#%v, unable to pack multi: %v", i, err)

		// If this is an invalid test case, and we passed it, then
		// we'll return an error.
		case err == nil && !versionCase.valid:
			t.Fatalf("#%v got nil error for invalid pack: %v",
				i, err)
		}

		// If this is a valid test case, then we'll continue to ensure
		// we can unpack it, and also that if we mutate the packed
		// version, then we trigger an error.
		if versionCase.valid {
			var unpackedMulti Multi
			err = unpackedMulti.UnpackFromReader(&b, keyRing)
			if err != nil {
				t.Fatalf("#%v unable to unpack multi: %v",
					i, err)
			}

			// First, we'll ensure that the unpacked version of the
			// packed multi is the same as the original set.
			if len(originalSingles) !=
				len(unpackedMulti.StaticBackups) {
				t.Fatalf("expected %v singles, got %v",
					len(originalSingles),
					len(unpackedMulti.StaticBackups))
			}
			for i := 0; i < numSingles; i++ {
				assertSingleEqual(
					t, originalSingles[i],
					unpackedMulti.StaticBackups[i],
				)
			}

			encrypter, err := lnencrypt.KeyRingEncrypter(keyRing)
			require.NoError(t, err)

			// Next, we'll make a fake packed multi, it'll have an
			// unknown version relative to what's implemented atm.
			var fakePackedMulti bytes.Buffer
			fakeRawMulti := bytes.NewBuffer(
				bytes.Repeat([]byte{99}, 20),
			)
			err = encrypter.EncryptPayloadToWriter(
				fakeRawMulti.Bytes(), &fakePackedMulti,
			)
			if err != nil {
				t.Fatalf("unable to pack fake multi; %v", err)
			}

			// We should reject this fake multi as it contains an
			// unknown version.
			err = unpackedMulti.UnpackFromReader(
				&fakePackedMulti, keyRing,
			)
			if err == nil {
				t.Fatalf("#%v unpack with unknown version "+
					"should have failed", i)
			}
		}
	}
}

// TestPackedMultiUnpack tests that we're able to properly unpack a typed
// packed multi.
func TestPackedMultiUnpack(t *testing.T) {
	t.Parallel()

	keyRing := &lnencrypt.MockKeyRing{}

	// First, we'll make a new unpacked multi with a random channel.
	testChannel, err := genRandomOpenChannelShell()
	require.NoError(t, err, "unable to gen random channel")
	var multi Multi
	multi.StaticBackups = append(
		multi.StaticBackups, NewSingle(testChannel, nil),
	)

	// Now that we have our multi, we'll pack it into a new buffer.
	var b bytes.Buffer
	if err := multi.PackToWriter(&b, keyRing); err != nil {
		t.Fatalf("unable to pack multi: %v", err)
	}

	// We should be able to properly unpack this typed packed multi.
	packedMulti := PackedMulti(b.Bytes())
	unpackedMulti, err := packedMulti.Unpack(keyRing)
	require.NoError(t, err, "unable to unpack multi")

	// Finally, the versions should match, and the unpacked singles also
	// identical.
	if multi.Version != unpackedMulti.Version {
		t.Fatalf("version mismatch: expected %v got %v",
			multi.Version, unpackedMulti.Version)
	}
	assertSingleEqual(
		t, multi.StaticBackups[0], unpackedMulti.StaticBackups[0],
	)
}
