// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package vault

import (
	"bytes"
	"reflect"
	"testing"
	"time"
)

func TestKeyring(t *testing.T) {
	k := NewKeyring()

	// Term should be 0
	if term := k.ActiveTerm(); term != 0 {
		t.Fatalf("bad: %d", term)
	}

	// Should have no key
	if key := k.ActiveKey(); key != nil {
		t.Fatalf("bad: %v", key)
	}

	// Add a key
	testKey := []byte("testing")
	key1 := &Key{Term: 1, Version: 1, Value: testKey, InstallTime: time.Now()}
	k, err := k.AddKey(key1)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Term should be 1
	if term := k.ActiveTerm(); term != 1 {
		t.Fatalf("bad: %d", term)
	}

	// Should have key
	key := k.ActiveKey()
	if key == nil {
		t.Fatalf("bad: %v", key)
	}
	if !bytes.Equal(key.Value, testKey) {
		t.Fatalf("bad: %v", key)
	}
	if tKey := k.TermKey(1); tKey != key {
		t.Fatalf("bad: %v", tKey)
	}

	// Should handle idempotent set
	k, err = k.AddKey(key1)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Should not allow conflicting set
	testConflict := []byte("nope")
	key1Conf := &Key{Term: 1, Version: 1, Value: testConflict, InstallTime: time.Now()}
	_, err = k.AddKey(key1Conf)
	if err == nil {
		t.Fatalf("err: %v", err)
	}

	// Add a new key
	testSecond := []byte("second")
	key2 := &Key{Term: 2, Version: 1, Value: testSecond, InstallTime: time.Now()}
	k, err = k.AddKey(key2)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Term should be 2
	if term := k.ActiveTerm(); term != 2 {
		t.Fatalf("bad: %d", term)
	}

	// Should have key
	newKey := k.ActiveKey()
	if newKey == nil {
		t.Fatalf("bad: %v", key)
	}
	if !bytes.Equal(newKey.Value, testSecond) {
		t.Fatalf("bad: %v", key)
	}
	if tKey := k.TermKey(2); tKey != newKey {
		t.Fatalf("bad: %v", tKey)
	}

	// Read of old key should work
	if tKey := k.TermKey(1); tKey != key {
		t.Fatalf("bad: %v", tKey)
	}

	// Remove the old key
	k, err = k.RemoveKey(1)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Read of old key should not work
	if tKey := k.TermKey(1); tKey != nil {
		t.Fatalf("bad: %v", tKey)
	}

	// Remove the active key should fail
	k, err = k.RemoveKey(2)
	if err == nil {
		t.Fatalf("err: %v", err)
	}
}

func TestKeyring_MasterKey(t *testing.T) {
	k := NewKeyring()
	master := []byte("test")
	master2 := []byte("test2")

	// Check no master
	out := k.RootKey()
	if out != nil {
		t.Fatalf("bad: %v", out)
	}

	// Set master
	k = k.SetRootKey(master)
	out = k.RootKey()
	if !bytes.Equal(out, master) {
		t.Fatalf("bad: %v", out)
	}

	// Update master
	k = k.SetRootKey(master2)
	out = k.RootKey()
	if !bytes.Equal(out, master2) {
		t.Fatalf("bad: %v", out)
	}
}

func TestKeyring_Serialize(t *testing.T) {
	k := NewKeyring()
	master := []byte("test")
	k = k.SetRootKey(master)

	now := time.Now()
	testKey := []byte("testing")
	testSecond := []byte("second")
	k, _ = k.AddKey(&Key{Term: 1, Version: 1, Value: testKey, InstallTime: now})
	k, _ = k.AddKey(&Key{Term: 2, Version: 1, Value: testSecond, InstallTime: now})

	buf, err := k.Serialize()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	k2, err := DeserializeKeyring(buf)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	out := k2.RootKey()
	if !bytes.Equal(out, master) {
		t.Fatalf("bad: %v", out)
	}

	if k2.ActiveTerm() != k.ActiveTerm() {
		t.Fatalf("Term mismatch")
	}

	var i uint32
	for i = 1; i < k.ActiveTerm(); i++ {
		key1 := k2.TermKey(i)
		key2 := k.TermKey(i)
		// Work around timezone bug due to DeepEqual using == for comparison
		if !key1.InstallTime.Equal(key2.InstallTime) {
			t.Fatalf("bad: key 1:\n%#v\nkey 2:\n%#v", key1, key2)
		}
		key1.InstallTime = key2.InstallTime
		if !reflect.DeepEqual(key1, key2) {
			t.Fatalf("bad: key 1:\n%#v\nkey 2:\n%#v", key1, key2)
		}
	}
}

func TestKey_Serialize(t *testing.T) {
	k := &Key{
		Term:        10,
		Version:     1,
		Value:       []byte("foobarbaz"),
		InstallTime: time.Now(),
	}

	buf, err := k.Serialize()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	out, err := DeserializeKey(buf)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Work around timezone bug due to DeepEqual using == for comparison
	if !k.InstallTime.Equal(out.InstallTime) {
		t.Fatalf("bad: expected:\n%#v\nactual:\n%#v", k, out)
	}
	k.InstallTime = out.InstallTime

	if !reflect.DeepEqual(k, out) {
		t.Fatalf("bad: %#v", out)
	}
}
