// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package libkb

import (
	"bytes"
	"encoding/base64"
	"testing"
	"time"

	"github.com/keybase/clockwork"

	"github.com/keybase/go-codec/codec"
	"github.com/keybase/go-crypto/openpgp"
	triplesec "github.com/keybase/go-triplesec"
)

type Foo struct {
	Bar int    `codec:"bar"`
	Baz string `codec:"baz"`
}

func TestDecode0(t *testing.T) {
	data := "gqNiYXIKo2JheqRzaGl0"
	bytes, err := base64.StdEncoding.DecodeString(data)
	var h codec.MsgpackHandle
	if err != nil {
		t.Errorf("error decoding: %s", err)
	}
	var foo Foo
	err = MsgpackDecodeAll(bytes, &h, &foo)
	if err != nil {
		t.Errorf("Failed to decode packet: %s", err)
	}
	if foo.Bar != 10 {
		t.Errorf("Bad decoding of our FOO")
	}

}

func TestDecode1(t *testing.T) {
	key := "[encoded data, 12804 characters]"
	bytes, err := base64.StdEncoding.DecodeString(key)
	if err != nil {
		t.Errorf("error decoding: %s", err)
	}
	_, err = DecodePacket(bytes)
	if err != nil {
		t.Errorf("Failed to decode packet: %s", err)
	}
}

func TestDecodeSKBSequence(t *testing.T) {
	seq := "[encoded data, 38416 characters]"
	buf := bytes.NewBufferString(seq)
	decoder := base64.NewDecoder(base64.StdEncoding, buf)
	packets, err := DecodePacketsUnchecked(decoder)
	if err != nil {
		t.Errorf("Failed to decode packets: %s", err)
	}
	p3skbs, err := packets.ToListOfSKBs()
	if err != nil {
		t.Errorf("Failed to make a list of SKBs: %s", err)
	}
	if !FastByteArrayEq(p3skbs[1].Pub, p3skbs[0].Pub) {
		t.Errorf("Expected a repeat of the same key")
	}
}

func makeTestLKSec(t *testing.T, gc *GlobalContext) *LKSec {
	_, pps, err := StretchPassphrase(gc, "makeTestLKSec", []byte("saltsaltsaltsalt"))
	if err != nil {
		t.Fatal(err)
	}
	lks := NewLKSec(pps, "1111111111111111111111111111111111111111111111111111111111111119", gc)
	if err := lks.GenerateServerHalf(); err != nil {
		t.Fatal(err)
	}

	return lks
}

func makeTestSKB(t *testing.T, lks *LKSec, g *GlobalContext) *SKB {
	email := "test@keybase.io"
	entity, err := openpgp.NewEntity("test name", "test comment", email, nil)
	if err != nil {
		t.Fatal(err)
	}

	skb, err := lks.ToSKB(NewGeneratedPGPKeyBundle(entity))
	if err != nil {
		t.Fatal(err)
	}
	skb.uid = lks.uid

	skb.newLKSecForTest = func(_ LKSecClientHalf) *LKSec {
		return lks
	}

	salt, err := RandBytes(triplesec.SaltLen)
	if err != nil {
		t.Fatal(err)
	}
	g.createLoginState()
	err = g.LoginState().Account(func(a *Account) {
		a.CreateLoginSessionWithSalt(email, salt)
	}, "makeTestSKB")
	if err != nil {
		t.Fatal(err)
	}

	return skb
}

func testPromptAndUnlock(t *testing.T, skb *SKB) {
	// XXX check nil, nil at end of this...
	parg := SecretKeyPromptArg{
		Reason:   "test reason",
		SecretUI: &TestSecretUI{Passphrase: "test passphrase", StoreSecret: true},
	}
	ss := NewSecretStore(skb.G(), "testusername")
	if ss == nil {
		t.Fatal("NewSecretStore returned nil")
	}
	key, err := skb.PromptAndUnlock(parg, ss, nil)
	if err != nil {
		t.Fatal(err)
	}

	if key == nil {
		t.Errorf("PromptAndUnlock returned a nil key")
	}
}

func TestBasicSecretStore(t *testing.T) {
	tc := SetupTest(t, "skb_basic_secret_store", 1)
	defer tc.Cleanup()

	lks := makeTestLKSec(t, tc.G)
	expectedSecret, err := lks.GetSecret(nil)
	if err != nil {
		t.Fatal(err)
	}

	skb := makeTestSKB(t, lks, tc.G)

	testPromptAndUnlock(t, skb)

	secret, _ := tc.G.SecretStoreAll.RetrieveSecret("testusername")
	if !secret.Equal(expectedSecret) {
		t.Errorf("secret doesn't match expected value")
	}

	// Doing the prompt again should retrieve the secret from our
	// store and not call skb.newLKSecForTest.

	skb = makeTestSKB(t, lks, tc.G)
	skb.newLKSecForTest = func(_ LKSecClientHalf) *LKSec {
		t.Errorf("newLKSecForTest unexpectedly called")
		return lks
	}
	testPromptAndUnlock(t, skb)
}

func TestCorruptSecretStore(t *testing.T) {
	tc := SetupTest(t, "skb_corrupt_secret_store", 1)
	defer tc.Cleanup()

	lks := makeTestLKSec(t, tc.G)
	expectedSecret, err := lks.GetSecret(nil)
	if err != nil {
		t.Fatal(err)
	}

	skb := makeTestSKB(t, lks, tc.G)
	fs, _ := newLKSecFullSecretFromBytes([]byte("corruptcorruptcorruptcorruptcorr"))
	tc.G.SecretStoreAll.StoreSecret("testusername", fs)
	testPromptAndUnlock(t, skb)

	// The corrupt secret value should be overwritten by the new
	// correct one.
	secret, _ := tc.G.SecretStoreAll.RetrieveSecret("testusername")
	if !secret.Equal(expectedSecret) {
		t.Errorf("secret doesn't match expected value")
	}
}

func TestUnusedSecretStore(t *testing.T) {
	tc := SetupTest(t, "skb_unused_secret_store", 1)
	defer tc.Cleanup()

	lks := makeTestLKSec(t, tc.G)

	skb := makeTestSKB(t, lks, tc.G)
	// It doesn't matter what passphraseStream contains, as long
	// as it's the right size.
	err := tc.G.LoginState().Account(func(a *Account) {
		a.CreateStreamCache(nil, NewPassphraseStream(make([]byte, extraLen)))
	}, "TestUnusedSecretStore")
	if err != nil {
		t.Fatal(err)
	}

	testPromptAndUnlock(t, skb)

	// Since there is a non-nil passphraseStream in the login
	// state, nothing should be stored in the secret store (since
	// no prompt was shown).
	secret, _ := tc.G.SecretStoreAll.RetrieveSecret("testusername")
	if !secret.IsNil() {
		t.Errorf("secret unexpectedly non-empty")
	}
}

func TestPromptCancelCache(t *testing.T) {
	tc := SetupTest(t, "prompt_cancel_cache", 1)
	defer tc.Cleanup()

	fakeClock := clockwork.NewFakeClock()
	tc.G.SetClock(fakeClock)

	lks := makeTestLKSec(t, tc.G)
	skb := makeTestSKB(t, lks, tc.G)

	ui := &TestCancelSecretUI{}
	err := testErrUnlock(t, skb, ui)
	if _, ok := err.(InputCanceledError); !ok {
		t.Errorf("PromptAndUnlock returned error %s (%T), expected InputCanceled", err, err)
	}
	if ui.CallCount != 1 {
		t.Errorf("GetPassphrase call count: %d, expected 1", ui.CallCount)
	}

	// try again 5s later: should still get an error, but CallCount should not increase
	fakeClock.Advance(5 * time.Second)
	err = testErrUnlock(t, skb, ui)
	if _, ok := err.(SkipSecretPromptError); !ok {
		t.Errorf("PromptAndUnlock returned %s (%T), expected SkipSecretPromptError", err, err)
	}
	if ui.CallCount != 1 {
		t.Errorf("GetPassphrase call count: %d, expected 1", ui.CallCount)
	}

	// wait 10 minutes: should get input canceled and CallCount should go up 1
	fakeClock.Advance(10 * time.Minute)
	err = testErrUnlock(t, skb, ui)
	if _, ok := err.(InputCanceledError); !ok {
		t.Errorf("PromptAndUnlock returned error %s (%T)", err, err)
	}
	if ui.CallCount != 2 {
		t.Errorf("GetPassphrase call count: %d, expected 2", ui.CallCount)
	}

	// try again 5s later: should still get an error, but CallCount should not increase
	fakeClock.Advance(5 * time.Second)
	err = testErrUnlock(t, skb, ui)
	if _, ok := err.(SkipSecretPromptError); !ok {
		t.Errorf("PromptAndUnlock returned %s (%T), expected SkipSecretPromptError", err, err)
	}
	if ui.CallCount != 2 {
		t.Errorf("GetPassphrase call count: %d, expected 1", ui.CallCount)
	}

	// wait 10 minutes: enter a passphrase this time
	fakeClock.Advance(10 * time.Minute)
	parg := SecretKeyPromptArg{
		Reason:   "test reason",
		SecretUI: &TestSecretUI{Passphrase: "passphrase"},
	}
	key, err := skb.PromptAndUnlock(parg, NewSecretStore(tc.G, "testusername"), nil)
	if err != nil {
		t.Fatal(err)
	}
	if key == nil {
		t.Errorf("PromptAndUnlock w/ passphrase failed to return a key")
	}
}

func testErrUnlock(t *testing.T, skb *SKB, ui *TestCancelSecretUI) error {
	parg := SecretKeyPromptArg{
		Reason:         "test reason",
		SecretUI:       ui,
		UseCancelCache: true,
	}
	key, err := skb.PromptAndUnlock(parg, NewSecretStore(skb.G(), "testusername"), nil)
	if err == nil {
		t.Fatal("PromptAndUnlock returned nil error")
	}
	if key != nil {
		t.Errorf("PromptAndUnlock returned a key (%v)", key)
	}
	return err
}
