package libkb

import (
	"encoding/json"
	"reflect"
	"testing"
	"time"

	keybase1 "github.com/keybase/client/go/protocol/keybase1"
	"github.com/stretchr/testify/require"
)

func TestMerkleRootPayloadUnmarshalWithSkips(t *testing.T) {
	raw := `{"body":{"kbfs":{"private":{"root":null,"version":null},"public":{"root":null,"version":null}},"key":{"fingerprint":"a05161510ee696601ba0ec7b3fd53b4871528cef","key_id":"3FD53B4871528CEF"},"legacy_uid_root":"75ac465f4cef9a32e593692c4c2db371401ed2cd7781b74a8210b49da116c514","prev":"a172068e89cfb73316e35ebee41adc3f42e882d042272553397e54208b7537e616cc16284ca714f442f459948af4660d43abc82a74b39a0b17c939739477856d","root":"04ae5a2268d8220a8a8a2392585bedeee6be1e81ad06d3b3355d0128725b52cf0f219e28f16781514e399b10dc0fb3a642a1b5ed66ba99b5e1f73df1be6e2d0b","seqno":228223,"skips":{"162687":"","195455":"","211839":"","220031":"","224127":"","226175":"","227199":"","227711":"fb99c7ba0844733318de3aaea7e19144c4ebc20365b5b90e8c443103c6975bd7","227967":"00b799c5df435ad5d746d52431f45f9522f137194d0ff8def84370b1ae5b5848","228095":"4ffd2cfdb108bdffaf61dd9e9c74a2ade6509509b96b852c2372f7fd25ed9188","228159":"cc57e780c9314377766855f324435e09e88c3d6f48c0ccdefb4da24915f50503","228191":"7032b5a573c3bece570fe5f3cfaa01a42ba673163898a4534c8a89dc0fb0954b","228207":"30ca14d7cdbfc444a141e82e2f02223d7e3226ab4626cfa00bc57fb5adf62ff7","228215":"e598fb8705203a24b6b1c722ddf61c9ca7055fe0d9c9f887186fec7148a2852d","228219":"bf7b127bf77224285492241654c3d1aea63a441fed64ac5d2a2e96ce89e3fa95","228221":"339edfa9f4fc2c6a5236bf390ef9906043684c579ccc36ef7b8ef88e330090cc","228222":"923f78febf3cc1134c382eb909d7bfc89968b40c94ca3f6a8402a0beef4199a1","97151":""},"txid":"e984c08f00430dab7200db56a84e8515","type":"merkle_root","version":1},"ctime":1484154393,"tag":"signature"}`
	var r MerkleRootPayloadUnpacked
	err := json.Unmarshal([]byte(raw), &r)
	if err != nil {
		t.Fatal(err)
	}
	var h NodeHash
	h, err = NodeHashFromHex(`fb99c7ba0844733318de3aaea7e19144c4ebc20365b5b90e8c443103c6975bd7`)
	if err != nil {
		t.Fatal(err)
	}
	if !hashEq(h, r.Body.Skips[227711]) {
		t.Fatal("should have had hash equality")
	}
}

func TestMerkleRootPayloadUnmarshalWithoutSkips(t *testing.T) {
	raw := `{"body":{"kbfs":{"private":{"root":"iKNlcGvEICJOVuOROjr8mEPkhETSMBpVZBJ41nBYBuodK/OrXGFaoWjEQINGJ9t9R8Eu4kLPLihrqqB21NDlieEMizSPcogzKeR3Q4Hsmt/uid87xpmvrZc4LhAQOX62sCEf3KTaSWjZG0Gjbm9uxBgfGYN0/evl0mbBib/jafJ/5yJIcDizzhiicHLEQMW85WnignmXsa5mLomnmduzUSksYOxUZU1bvJwn9Isj5Tyw2AL4t4t3nzgF1VEricihIF5N7+Pih/HlO0HAFE6ic27NHi2hdAKidHPOWHespKF2AQ==","version":1},"public":{"root":"hqFoxEBK5PjCumUTQuUrwDPI08l8x+QgiEqq5AbRVQGimEjDozDf6ax5mKh/A3qGv5Sjgxqkni36/rKxE6JBODpXJwJjonByxEBpdeld3+IbKVY7ACqtETV9cXP1UPO+Sxj+zJ2PdmmuelBZIYcW3FgcGiPJm15d/rpnSq2ob4m7u8B1wgWeLpcbonNuzR42oXQBonRzzlh3rJehdgE=","version":1}},"key":{"fingerprint":"03e146cdaf8136680ad566912a32340cec8c9492","key_id":"2A32340CEC8C9492"},"legacy_uid_root":"0c58a74abcb1db656146ebf667b5ea5cc55e208fbb534cd1b1e9454890358e5e","prev":"f82f0d1c236703205690110203e34cd9fc5dfb7a6bef386e81a1c9301b101deea176fae530ca654424cdd72565f1dc196af1eaed203b7dd0a100d672080b1961","root":"976a48126ced7ba136ef1a33e337c9ad22ca233eb19d46a5a1de482653b0ac25e35d3e55f1cb1fc5a29e62df5612581909afe8a26dc44710da5094445f009af0","seqno":796746,"txid":"db7ad0824c5753c28bc849d60e711815","type":"merkle_root","version":1},"ctime":1484239402,"tag":"signature"}`
	var r MerkleRootPayloadUnpacked
	err := json.Unmarshal([]byte(raw), &r)
	if err != nil {
		t.Fatal(err)
	}
	if r.Body.Skips != nil {
		t.Fatal("expected an empty skip table")
	}
}

func TestMerkleSkipVectors(t *testing.T) {
	tc := SetupTest(t, "TestMerkleSkipVectors", 1)
	defer tc.Cleanup()
	for i, v := range merkleSkipTestVectors {
		ss, err := readSkipSequenceFromStringList(v.data)
		if err != nil {
			t.Fatal(err)
		}
		err = ss.verify(NewMetaContextForTest(tc), skipTestVectorsThisRoot, skipTestVectorsLastRoot)

		tc.G.Log.Info("Iteration %d: test %s: %v", i, v.name, err)

		if v.e == merkleErrorNone && err != nil {
			t.Fatalf("Expected no error in test %s, but got %s", v.name, err)
		}

		if v.e == merkleErrorNone {
			continue
		}
		me, ok := err.(MerkleClientError)
		if !ok {
			t.Fatalf("Got an unexpected error type in test %s: %T (%v)", v.name, err, err)
		}
		if me.t != v.e {
			t.Fatalf("Got wrong error type in test %s: %v != %v", v.name, me.t, v.e)
		}
	}
}

func TestComputeSetBitsBigEndian(t *testing.T) {
	tests := []struct {
		x        uint
		expected []uint
	}{
		{0, nil},
		{1, []uint{1}},
		{10, []uint{2, 8}},
		{500, []uint{4, 16, 32, 64, 128, 256}},
		{1023, []uint{1, 2, 4, 8, 16, 32, 64, 128, 256, 512}},
		{1024, []uint{1024}},
		{1025, []uint{1, 1024}},
		{20000, []uint{32, 512, 1024, 2048, 16384}},
	}
	for _, test := range tests {
		got := computeSetBitsBigEndian(test.x)
		if !reflect.DeepEqual(got, test.expected) {
			t.Fatalf("Failed on input %d, expected %v, got %v.", test.x, test.expected, got)
		}
	}
}

func TestComputeLogPatternMerkleSkips(t *testing.T) {
	tests := []struct {
		start    int
		end      int
		expected []uint
	}{
		{100, 2033, []uint{1009, 497, 241, 113, 105, 101}},
		{100, 102, nil},
		{100, 103, []uint{101}},
	}
	for _, test := range tests {
		got, err := computeLogPatternMerkleSkips(keybase1.Seqno(test.start), keybase1.Seqno(test.end))
		require.NoError(t, err)
		if !reflect.DeepEqual(got, test.expected) {
			t.Fatalf("Failed on input (%d, %d), expected %v, got %v.", test.start, test.end, test.expected, got)
		}
	}

	_, err := computeLogPatternMerkleSkips(keybase1.Seqno(1000), keybase1.Seqno(999))
	require.Error(t, err)
	require.Contains(t, err.Error(), "startSeqno > endSeqno")
}

func TestComputeExpectedRootSkips(t *testing.T) {
	tests := []struct {
		root     int
		expected []uint
	}{
		{0, nil},
		{1, nil},
		{100, []uint{99, 98, 96, 92, 84, 68, 36}},
		{255, []uint{254, 253, 251, 247, 239, 223, 191, 127}},
		{256, []uint{255, 254, 252, 248, 240, 224, 192, 128}},
		{257, []uint{256, 255, 253, 249, 241, 225, 193, 129, 1}},
		{1000, []uint{999, 998, 996, 992, 984, 968, 936, 872, 744, 488}},
		{2048, []uint{2047, 2046, 2044, 2040, 2032, 2016, 1984, 1920, 1792, 1536, 1024}},
	}
	for _, test := range tests {
		got := computeExpectedRootSkips(uint(test.root))
		if !reflect.DeepEqual(got, test.expected) {
			t.Fatalf("Failed on input (%d), expected %v, got %v.", test.root, test.expected, got)
		}
	}
}

func TestFetchRootFromServer(t *testing.T) {
	tc := SetupTest(t, "MerkleClient", 0)
	defer tc.Cleanup()

	mc := tc.G.GetMerkleClient()

	root, err := mc.FetchRootFromServer(NewMetaContextForTest(tc), 0)
	require.NoError(t, err)
	require.NotNil(t, root)

	origAPI := tc.G.API

	// Ensure these calls do not go to the server but still succeed (by switching the api interface)
	tc.G.API = &ErrorMockAPI{}

	rootNew, err := mc.FetchRootFromServer(NewMetaContextForTest(tc), 30*time.Second)
	require.NoError(t, err)
	require.Equal(t, *root, *rootNew)

	rootNew, err = mc.FetchRootFromServerByMinSeqno(NewMetaContextForTest(tc), *root.Seqno())
	require.NoError(t, err)
	require.Equal(t, *root, *rootNew)

	rootNew, err = mc.FetchRootFromServerByMinSeqno(NewMetaContextForTest(tc), *root.Seqno()-3)
	require.NoError(t, err)
	require.Equal(t, *root, *rootNew)

	// These calls should instead result in api calls to the server and fail
	rootNew, err = mc.FetchRootFromServer(NewMetaContextForTest(tc), 0)
	require.Error(t, err)
	require.Equal(t, errMockAPI, err)
	require.Nil(t, rootNew)

	rootNew, err = mc.FetchRootFromServer(NewMetaContextForTest(tc), 1*time.Nanosecond)
	require.Error(t, err)
	require.Equal(t, errMockAPI, err)
	require.Nil(t, rootNew)

	rootNew, err = mc.FetchRootFromServerByMinSeqno(NewMetaContextForTest(tc), *root.Seqno()+3)
	require.Error(t, err)
	require.Equal(t, errMockAPI, err)
	require.Nil(t, rootNew)

	// if we put the api back, the call should succeed again
	tc.G.API = origAPI
	_, err = mc.FetchRootFromServerByMinSeqno(NewMetaContextForTest(tc), 0)
	require.NoError(t, err)
}
