// Copyright (c) 2024 Warden Project
// SPDX-License-Identifier: MPL-2.0

package core

import (
	"context"
	"crypto/rand"
	"reflect"
	"testing"
	"time"

	"github.com/openbao/openbao/sdk/v2/logical"
	"github.com/openbao/openbao/sdk/v2/physical/inmem"
	"github.com/stretchr/testify/require"
)

func testBarrier(t *testing.T, b SecurityBarrier) {
	e, key, err := testInitAndUnseal(t, b)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Operations should work
	out, err := b.Get(context.Background(), "test")
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if out != nil {
		t.Fatalf("bad: %v", out)
	}

	// List should have only "core/"
	keys, err := b.List(context.Background(), "")
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if len(keys) != 1 || keys[0] != "core/" {
		t.Fatalf("bad: %v", keys)
	}

	// Try to write
	if err := b.Put(context.Background(), e); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Should be equal
	out, err = b.Get(context.Background(), "test")
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if !reflect.DeepEqual(out, e) {
		t.Fatalf("bad: %v exp: %v", out, e)
	}

	// List should show the items
	keys, err = b.List(context.Background(), "")
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if len(keys) != 2 {
		t.Fatalf("bad: %v", keys)
	}
	if keys[0] != "core/" || keys[1] != "test" {
		t.Fatalf("bad: %v", keys)
	}

	// Delete should clear
	err = b.Delete(context.Background(), "test")
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Double Delete is fine
	err = b.Delete(context.Background(), "test")
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Should be nil
	out, err = b.Get(context.Background(), "test")
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if out != nil {
		t.Fatalf("bad: %v", out)
	}

	// List should have nothing
	keys, err = b.List(context.Background(), "")
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if len(keys) != 1 || keys[0] != "core/" {
		t.Fatalf("bad: %v", keys)
	}

	// Add the item back
	if err := b.Put(context.Background(), e); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Reseal should prevent any updates
	if err := b.Seal(); err != nil {
		t.Fatalf("err: %v", err)
	}

	// No access allowed
	if _, err := b.Get(context.Background(), "test"); err != ErrBarrierSealed {
		t.Fatalf("err: %v", err)
	}

	// Unseal should work
	if err := b.Unseal(context.Background(), key); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Should be equal
	out, err = b.Get(context.Background(), "test")
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if !reflect.DeepEqual(out, e) {
		t.Fatalf("bad: %v exp: %v", out, e)
	}

	// Final cleanup
	err = b.Delete(context.Background(), "test")
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Reseal should prevent any updates
	if err := b.Seal(); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Modify the key
	key[0]++

	// Unseal should fail
	if err := b.Unseal(context.Background(), key); err != ErrBarrierInvalidKey {
		t.Fatalf("err: %v", err)
	}
}

func testInitAndUnseal(t *testing.T, b SecurityBarrier) (*logical.StorageEntry, []byte, error) {
	// Should not be initialized
	init, err := b.Initialized(context.Background())
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if init {
		t.Fatal("should not be initialized")
	}

	// Should start sealed
	sealed, err := b.Sealed()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if !sealed {
		t.Fatal("should be sealed")
	}

	// Sealing should be a no-op
	if err := b.Seal(); err != nil {
		t.Fatalf("err: %v", err)
	}

	// All operations should fail
	e := &logical.StorageEntry{Key: "test", Value: []byte("test")}
	if err := b.Put(context.Background(), e); err != ErrBarrierSealed {
		t.Fatalf("err: %v", err)
	}
	if _, err := b.Get(context.Background(), "test"); err != ErrBarrierSealed {
		t.Fatalf("err: %v", err)
	}
	if err := b.Delete(context.Background(), "test"); err != ErrBarrierSealed {
		t.Fatalf("err: %v", err)
	}
	if _, err := b.List(context.Background(), ""); err != ErrBarrierSealed {
		t.Fatalf("err: %v", err)
	}

	// Get a new key
	key, err := b.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Validate minimum key length
	min, max := b.KeyLength()
	if min < 16 {
		t.Fatalf("minimum key size too small: %d", min)
	}
	if max < min {
		t.Fatal("maximum key size smaller than min")
	}

	// Unseal should not work
	if err := b.Unseal(context.Background(), key); err != ErrBarrierNotInit {
		t.Fatalf("err: %v", err)
	}

	// Initialize the vault
	if err := b.Initialize(context.Background(), key, nil, rand.Reader); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Double Initialize should fail
	if err := b.Initialize(context.Background(), key, nil, rand.Reader); err != ErrBarrierAlreadyInit {
		t.Fatalf("err: %v", err)
	}

	// Should be initialized
	init, err = b.Initialized(context.Background())
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if !init {
		t.Fatal("should be initialized")
	}

	// Should still be sealed
	sealed, err = b.Sealed()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if !sealed {
		t.Fatal("should sealed")
	}

	// Unseal should work
	if err := b.Unseal(context.Background(), key); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Unseal should no-op when done twice
	if err := b.Unseal(context.Background(), key); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Should no longer be sealed
	sealed, err = b.Sealed()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if sealed {
		t.Fatal("should be unsealed")
	}

	// Verify the root key
	if err := b.VerifyRoot(key); err != nil {
		t.Fatalf("err: %v", err)
	}
	return e, key, err
}

func testBarrier_Rotate(t *testing.T, b SecurityBarrier) {
	// Initialize the barrier
	key, _ := b.GenerateKey(rand.Reader)
	b.Initialize(context.Background(), key, nil, rand.Reader)
	err := b.Unseal(context.Background(), key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Check the key info
	info, err := b.ActiveKeyInfo()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if info.Term != 1 {
		t.Fatalf("Bad term: %d", info.Term)
	}
	if time.Since(info.InstallTime) > time.Second {
		t.Fatalf("Bad install: %v", info.InstallTime)
	}
	first := info.InstallTime

	// Write a key
	e1 := &logical.StorageEntry{Key: "test", Value: []byte("test")}
	if err := b.Put(context.Background(), e1); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Rotate the encryption key
	newTerm, err := b.Rotate(context.Background(), rand.Reader)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if newTerm != 2 {
		t.Fatalf("bad: %v", newTerm)
	}

	// Check the key info
	info, err = b.ActiveKeyInfo()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if info.Term != 2 {
		t.Fatalf("Bad term: %d", info.Term)
	}
	if !info.InstallTime.After(first) {
		t.Fatalf("Bad install: %v", info.InstallTime)
	}

	// Write another key
	e2 := &logical.StorageEntry{Key: "foo", Value: []byte("test")}
	if err := b.Put(context.Background(), e2); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Reading both should work
	out, err := b.Get(context.Background(), e1.Key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if out == nil {
		t.Fatalf("bad: %v", out)
	}

	out, err = b.Get(context.Background(), e2.Key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if out == nil {
		t.Fatalf("bad: %v", out)
	}

	// Seal and unseal
	err = b.Seal()
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	err = b.Unseal(context.Background(), key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Reading both should work
	out, err = b.Get(context.Background(), e1.Key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if out == nil {
		t.Fatalf("bad: %v", out)
	}

	out, err = b.Get(context.Background(), e2.Key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if out == nil {
		t.Fatalf("bad: %v", out)
	}

	// Should be fine to reload keyring
	err = b.ReloadKeyring(context.Background())
	if err != nil {
		t.Fatalf("err: %v", err)
	}
}

func testBarrier_RotateRootKey(t *testing.T, b SecurityBarrier) {
	// Initialize the barrier
	key, _ := b.GenerateKey(rand.Reader)
	b.Initialize(context.Background(), key, nil, rand.Reader)
	err := b.Unseal(context.Background(), key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Write a key
	e1 := &logical.StorageEntry{Key: "test", Value: []byte("test")}
	if err := b.Put(context.Background(), e1); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Verify the root key
	if err := b.VerifyRoot(key); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Rotate to a new root key
	newKey, _ := b.GenerateKey(rand.Reader)
	err = b.RotateRootKey(context.Background(), newKey)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Verify the old root key
	if err := b.VerifyRoot(key); err != ErrBarrierInvalidKey {
		t.Fatalf("err: %v", err)
	}

	// Verify the new root key
	if err := b.VerifyRoot(newKey); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Reading should work
	out, err := b.Get(context.Background(), e1.Key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if out == nil {
		t.Fatalf("bad: %v", out)
	}

	// Seal
	err = b.Seal()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Unseal with old key should fail
	err = b.Unseal(context.Background(), key)
	if err == nil {
		t.Fatal("unseal should fail")
	}

	// Unseal with new keys should work
	err = b.Unseal(context.Background(), newKey)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Reading should work
	out, err = b.Get(context.Background(), e1.Key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if out == nil {
		t.Fatalf("bad: %v", out)
	}

	// Should be fine to reload keyring
	err = b.ReloadKeyring(context.Background())
	if err != nil {
		t.Fatalf("err: %v", err)
	}
}

func testBarrier_Upgrade(t *testing.T, b1, b2 SecurityBarrier) {
	// Initialize the barrier
	key, _ := b1.GenerateKey(rand.Reader)
	b1.Initialize(context.Background(), key, nil, rand.Reader)
	err := b1.Unseal(context.Background(), key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	err = b2.Unseal(context.Background(), key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Rotate the encryption key
	newTerm, err := b1.Rotate(context.Background(), rand.Reader)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Create upgrade path
	err = b1.CreateUpgrade(context.Background(), newTerm)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Check for an upgrade
	did, updated, err := b2.CheckUpgrade(context.Background())
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if !did || updated != newTerm {
		t.Fatal("failed to upgrade")
	}

	// Should have no upgrades pending
	did, updated, err = b2.CheckUpgrade(context.Background())
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if did {
		t.Fatal("should not have upgrade")
	}
	require.EqualValues(t, 0, updated)

	// Rotate the encryption key
	newTerm, err = b1.Rotate(context.Background(), rand.Reader)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Create upgrade path
	err = b1.CreateUpgrade(context.Background(), newTerm)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Destroy upgrade path
	err = b1.DestroyUpgrade(context.Background(), newTerm)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Should have no upgrades pending
	did, updated, err = b2.CheckUpgrade(context.Background())
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if did {
		t.Fatal("should not have upgrade")
	}
	require.EqualValues(t, 0, updated)
}

func testBarrier_Upgrade_RotateRootKey(t *testing.T, b1, b2 SecurityBarrier) {
	// Initialize the barrier
	key, _ := b1.GenerateKey(rand.Reader)
	b1.Initialize(context.Background(), key, nil, rand.Reader)
	err := b1.Unseal(context.Background(), key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	err = b2.Unseal(context.Background(), key)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Rotate to a new root key
	newKey, _ := b1.GenerateKey(rand.Reader)
	err = b1.RotateRootKey(context.Background(), newKey)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Reload the root key
	err = b2.ReloadRootKey(context.Background())
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Reload the keyring
	err = b2.ReloadKeyring(context.Background())
	if err != nil {
		t.Fatalf("err: %v", err)
	}
}

// TestAESGCMBarrier tests the AESGCMBarrier implementation
func TestAESGCMBarrier(t *testing.T) {
	// Create in-memory backend
	backend, err := inmem.NewInmem(nil, nil)
	if err != nil {
		t.Fatalf("failed to create backend: %v", err)
	}

	// Create the barrier
	b, err := NewAESGCMBarrier(backend)
	if err != nil {
		t.Fatalf("failed to create barrier: %v", err)
	}

	// Run the test suite
	testBarrier(t, b)
}

// TestAESGCMBarrier_Rotate tests encryption key rotation
func TestAESGCMBarrier_Rotate(t *testing.T) {
	// Create in-memory backend
	backend, err := inmem.NewInmem(nil, nil)
	if err != nil {
		t.Fatalf("failed to create backend: %v", err)
	}

	// Create the barrier
	b, err := NewAESGCMBarrier(backend)
	if err != nil {
		t.Fatalf("failed to create barrier: %v", err)
	}

	// Run the rotation test
	testBarrier_Rotate(t, b)
}

// TestAESGCMBarrier_RotateRootKey tests root key rotation
func TestAESGCMBarrier_RotateRootKey(t *testing.T) {
	// Create in-memory backend
	backend, err := inmem.NewInmem(nil, nil)
	if err != nil {
		t.Fatalf("failed to create backend: %v", err)
	}

	// Create the barrier
	b, err := NewAESGCMBarrier(backend)
	if err != nil {
		t.Fatalf("failed to create barrier: %v", err)
	}

	// Run the root key rotation test
	testBarrier_RotateRootKey(t, b)
}

// TestAESGCMBarrier_Upgrade tests upgrade path creation and checking
func TestAESGCMBarrier_Upgrade(t *testing.T) {
	// Create shared in-memory backend
	backend, err := inmem.NewInmem(nil, nil)
	if err != nil {
		t.Fatalf("failed to create backend: %v", err)
	}

	// Create two barriers sharing the same backend
	b1, err := NewAESGCMBarrier(backend)
	if err != nil {
		t.Fatalf("failed to create barrier 1: %v", err)
	}

	b2, err := NewAESGCMBarrier(backend)
	if err != nil {
		t.Fatalf("failed to create barrier 2: %v", err)
	}

	// Run the upgrade test
	testBarrier_Upgrade(t, b1, b2)
}

// TestAESGCMBarrier_Upgrade_RotateRootKey tests upgrade with root key rotation
func TestAESGCMBarrier_Upgrade_RotateRootKey(t *testing.T) {
	// Create shared in-memory backend
	backend, err := inmem.NewInmem(nil, nil)
	if err != nil {
		t.Fatalf("failed to create backend: %v", err)
	}

	// Create two barriers sharing the same backend
	b1, err := NewAESGCMBarrier(backend)
	if err != nil {
		t.Fatalf("failed to create barrier 1: %v", err)
	}

	b2, err := NewAESGCMBarrier(backend)
	if err != nil {
		t.Fatalf("failed to create barrier 2: %v", err)
	}

	// Run the upgrade with root key rotation test
	testBarrier_Upgrade_RotateRootKey(t, b1, b2)
}
