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

package transit

import (
	"testing"

	"github.com/hashicorp/vault/helper/namespace"
	"github.com/hashicorp/vault/sdk/helper/keysutil"
	"github.com/hashicorp/vault/sdk/logical"
)

func TestTransit_Trim(t *testing.T) {
	b, storage := createBackendWithSysView(t)

	doReq := func(t *testing.T, req *logical.Request) *logical.Response {
		t.Helper()
		resp, err := b.HandleRequest(namespace.RootContext(nil), req)
		if err != nil || (resp != nil && resp.IsError()) {
			t.Fatalf("got err:\n%#v\nresp:\n%#v\n", err, resp)
		}
		return resp
	}
	doErrReq := func(t *testing.T, req *logical.Request) {
		t.Helper()
		resp, err := b.HandleRequest(namespace.RootContext(nil), req)
		if err == nil && (resp == nil || !resp.IsError()) {
			t.Fatalf("expected error; resp:\n%#v\n", resp)
		}
	}

	// Create a key
	req := &logical.Request{
		Path:      "keys/aes",
		Storage:   storage,
		Operation: logical.UpdateOperation,
	}
	doReq(t, req)

	// Get the policy and check that the archive has correct number of keys
	p, _, err := b.GetPolicy(namespace.RootContext(nil), keysutil.PolicyRequest{
		Storage: storage,
		Name:    "aes",
	}, b.GetRandomReader())
	if err != nil {
		t.Fatal(err)
	}

	// Archive: 0, 1
	archive, err := p.LoadArchive(namespace.RootContext(nil), storage)
	if err != nil {
		t.Fatal(err)
	}
	// Index "0" in the archive is unused. Hence the length of the archived
	// keys will always be 1 more than the actual number of keys.
	if len(archive.Keys) != 2 {
		t.Fatalf("bad: len of archived keys; expected: 2, actual: %d", len(archive.Keys))
	}

	// Ensure that there are 5 key versions, by rotating the key 4 times
	for i := 0; i < 4; i++ {
		req.Path = "keys/aes/rotate"
		req.Data = nil
		doReq(t, req)
	}

	// Archive: 0, 1, 2, 3, 4, 5
	archive, err = p.LoadArchive(namespace.RootContext(nil), storage)
	if err != nil {
		t.Fatal(err)
	}
	if len(archive.Keys) != 6 {
		t.Fatalf("bad: len of archived keys; expected: 6, actual: %d", len(archive.Keys))
	}

	// Min available version should not be set when min_encryption_version is not
	// set
	req.Path = "keys/aes/trim"
	req.Data = map[string]interface{}{
		"min_available_version": 1,
	}
	doErrReq(t, req)

	// Set min_encryption_version to 0
	req.Path = "keys/aes/config"
	req.Data = map[string]interface{}{
		"min_encryption_version": 0,
	}
	doReq(t, req)

	// Min available version should not be converted to 0 for nil values
	req.Path = "keys/aes/trim"
	req.Data = map[string]interface{}{
		"min_available_version": nil,
	}
	doErrReq(t, req)

	// Set min_encryption_version to 4
	req.Path = "keys/aes/config"
	req.Data = map[string]interface{}{
		"min_encryption_version": 4,
	}
	doReq(t, req)

	// Set min_decryption_version to 3
	req.Data = map[string]interface{}{
		"min_decryption_version": 3,
	}
	doReq(t, req)

	// Min available version cannot be greater than min encryption version
	req.Path = "keys/aes/trim"
	req.Data = map[string]interface{}{
		"min_available_version": 5,
	}
	doErrReq(t, req)

	// Min available version cannot be greater than min decryption version
	req.Data["min_available_version"] = 4
	doErrReq(t, req)

	// Min available version cannot be negative
	req.Data["min_available_version"] = -1
	doErrReq(t, req)

	// Min available version should be positive
	req.Data["min_available_version"] = 0
	doErrReq(t, req)

	// Trim all keys before version 3. Index 0 and index 1 will be deleted from
	// archived keys.
	req.Data["min_available_version"] = 3
	doReq(t, req)

	// Archive: 3, 4, 5
	archive, err = p.LoadArchive(namespace.RootContext(nil), storage)
	if err != nil {
		t.Fatal(err)
	}
	if len(archive.Keys) != 3 {
		t.Fatalf("bad: len of archived keys; expected: 3, actual: %d", len(archive.Keys))
	}

	// Min decryption version should not be less than min available version
	req.Path = "keys/aes/config"
	req.Data = map[string]interface{}{
		"min_decryption_version": 1,
	}
	doErrReq(t, req)

	// Min encryption version should not be less than min available version
	req.Data = map[string]interface{}{
		"min_encryption_version": 2,
	}
	doErrReq(t, req)

	// Rotate 5 more times
	for i := 0; i < 5; i++ {
		doReq(t, &logical.Request{
			Path:      "keys/aes/rotate",
			Storage:   storage,
			Operation: logical.UpdateOperation,
		})
	}

	// Archive: 3, 4, 5, 6, 7, 8, 9, 10
	archive, err = p.LoadArchive(namespace.RootContext(nil), storage)
	if err != nil {
		t.Fatal(err)
	}
	if len(archive.Keys) != 8 {
		t.Fatalf("bad: len of archived keys; expected: 8, actual: %d", len(archive.Keys))
	}

	// Set min encryption version to 7
	req.Data = map[string]interface{}{
		"min_encryption_version": 7,
	}
	doReq(t, req)

	// Set min decryption version to 7
	req.Data = map[string]interface{}{
		"min_decryption_version": 7,
	}
	doReq(t, req)

	// Trim all versions before 7
	req.Path = "keys/aes/trim"
	req.Data = map[string]interface{}{
		"min_available_version": 7,
	}
	doReq(t, req)

	// Archive: 7, 8, 9, 10
	archive, err = p.LoadArchive(namespace.RootContext(nil), storage)
	if err != nil {
		t.Fatal(err)
	}
	if len(archive.Keys) != 4 {
		t.Fatalf("bad: len of archived keys; expected: 4, actual: %d", len(archive.Keys))
	}

	// Read the key
	req.Path = "keys/aes"
	req.Operation = logical.ReadOperation
	resp := doReq(t, req)
	keys := resp.Data["keys"].(map[string]int64)
	if len(keys) != 4 {
		t.Fatalf("bad: number of keys; expected: 4, actual: %d", len(keys))
	}

	// Test if moving the min_encryption_version and min_decryption_versions
	// are working fine

	// Set min encryption version to 10
	req.Path = "keys/aes/config"
	req.Operation = logical.UpdateOperation
	req.Data = map[string]interface{}{
		"min_encryption_version": 10,
	}
	doReq(t, req)
	if p.MinEncryptionVersion != 10 {
		t.Fatalf("failed to set min encryption version")
	}

	// Set min decryption version to 9
	req.Data = map[string]interface{}{
		"min_decryption_version": 9,
	}
	doReq(t, req)
	if p.MinDecryptionVersion != 9 {
		t.Fatalf("failed to set min encryption version")
	}

	// Reduce the min decryption version to 8
	req.Data = map[string]interface{}{
		"min_decryption_version": 8,
	}
	doReq(t, req)
	if p.MinDecryptionVersion != 8 {
		t.Fatalf("failed to set min encryption version")
	}

	// Reduce the min encryption version to 8
	req.Data = map[string]interface{}{
		"min_encryption_version": 8,
	}
	doReq(t, req)
	if p.MinDecryptionVersion != 8 {
		t.Fatalf("failed to set min decryption version")
	}

	// Read the key to ensure that the keys are properly copied from the
	// archive into the policy
	req.Path = "keys/aes"
	req.Operation = logical.ReadOperation
	resp = doReq(t, req)
	keys = resp.Data["keys"].(map[string]int64)
	if len(keys) != 3 {
		t.Fatalf("bad: number of keys; expected: 3, actual: %d", len(keys))
	}

	// Ensure that archive has remained unchanged
	// Archive: 7, 8, 9, 10
	archive, err = p.LoadArchive(namespace.RootContext(nil), storage)
	if err != nil {
		t.Fatal(err)
	}
	if len(archive.Keys) != 4 {
		t.Fatalf("bad: len of archived keys; expected: 4, actual: %d", len(archive.Keys))
	}
}
