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

package kv

import (
	"context"
	"net/http"
	"testing"

	"github.com/go-test/deep"
	logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
	"github.com/hashicorp/vault/api"
	vaulthttp "github.com/hashicorp/vault/http"
	"github.com/hashicorp/vault/sdk/logical"
	"github.com/hashicorp/vault/vault"
)

// TestKV_Subkeys_NotFound issues a read to the subkeys endpoint for a path
// that does not exist. A 400 status should be returned.
func TestKV_Subkeys_NotFound(t *testing.T) {
	coreConfig := &vault.CoreConfig{
		LogicalBackends: map[string]logical.Factory{
			"kv": logicalKv.VersionedKVFactory,
		},
	}

	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
		HandlerFunc: vaulthttp.Handler,
	})

	cluster.Start()
	defer cluster.Cleanup()

	cores := cluster.Cores

	core := cores[0].Core
	c := cluster.Cores[0].Client
	vault.TestWaitActive(t, core)

	// Mount a KVv2 backend
	err := c.Sys().Mount("kv", &api.MountInput{
		Type: "kv-v2",
	})
	if err != nil {
		t.Fatal(err)
	}

	apiRespRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
		req := c.NewRequest("GET", "/v1/kv/subkeys/foo")
		return c.RawRequestWithContext(context.Background(), req)
	})

	apiResp, ok := apiRespRaw.(*api.Response)
	if !ok {
		t.Fatalf("response not an api.Response, actual: %#v", apiRespRaw)
	}

	if err == nil || apiResp == nil {
		t.Fatalf("expected subkeys request to fail, err :%v, resp: %#v", err, apiResp)
	}

	if apiResp.StatusCode != http.StatusNotFound {
		t.Fatalf("expected subkeys request to fail with %d status code, resp: %#v", http.StatusNotFound, apiResp)
	}
}

// TestKV_Subkeys_Deleted writes a single version of a secret to the KVv2
// secret engine. The secret is subsequently deleted. A read to the subkeys
// endpoint should return a 400 status with a nil "subkeys" value and the
// "deletion_time" key in the "metadata" key should be not be empty.
func TestKV_Subkeys_Deleted(t *testing.T) {
	coreConfig := &vault.CoreConfig{
		LogicalBackends: map[string]logical.Factory{
			"kv": logicalKv.VersionedKVFactory,
		},
	}

	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
		HandlerFunc: vaulthttp.Handler,
	})

	cluster.Start()
	defer cluster.Cleanup()

	cores := cluster.Cores

	core := cores[0].Core
	c := cluster.Cores[0].Client
	vault.TestWaitActive(t, core)

	// Mount a KVv2 backend
	err := c.Sys().Mount("kv", &api.MountInput{
		Type: "kv-v2",
	})
	if err != nil {
		t.Fatal(err)
	}

	kvData := map[string]interface{}{
		"data": map[string]interface{}{
			"bar": "a",
		},
	}

	resp, err := kvRequestWithRetry(t, func() (interface{}, error) {
		return c.Logical().Write("kv/data/foo", kvData)
	})
	if err != nil {
		t.Fatalf("write failed, err :%v, resp: %#v", err, resp)
	}

	secretRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
		return c.Logical().Delete("kv/data/foo")
	})
	if err != nil {
		t.Fatalf("delete failed, err :%v, resp: %#v", err, secretRaw)
	}

	apiRespRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
		req := c.NewRequest("GET", "/v1/kv/subkeys/foo")
		return c.RawRequestWithContext(context.Background(), req)
	})

	apiResp, ok := apiRespRaw.(*api.Response)
	if !ok {
		t.Fatalf("response not a api.Response, actual: %#v", apiRespRaw)
	}

	if apiResp != nil {
		defer apiResp.Body.Close()
	}

	if err == nil || apiResp == nil {
		t.Fatalf("expected subkeys request to fail, err :%v, resp: %#v", err, apiResp)
	}

	if apiResp.StatusCode != http.StatusNotFound {
		t.Fatalf("expected subkeys request to fail with %d status code, resp: %#v", http.StatusNotFound, apiResp)
	}

	secret, err := api.ParseSecret(apiResp.Body)
	if err != nil {
		t.Fatalf("failed to parse resp body, err: %v", err)
	}

	subkeys, ok := secret.Data["subkeys"]
	if !ok {
		t.Fatalf("key \"subkeys\" not found in response")
	}

	if subkeys != nil {
		t.Fatalf("expected nil subkeys, actual: %#v", subkeys)
	}

	metadata, ok := secret.Data["metadata"].(map[string]interface{})

	if !ok {
		t.Fatalf("metadata not present in response or invalid, metadata: %#v", secret.Data["metadata"])
	}

	if deletionTime, ok := metadata["deletion_time"].(string); !ok || deletionTime == "" {
		t.Fatalf("metadata does not contain deletion time, metadata: %#v", metadata)
	}
}

// TestKV_Subkeys_Destroyed writes a single version of a secret to the KVv2
// secret engine. The secret is subsequently destroyed. A read to the subkeys
// endpoint should return a 400 status with a nil "subkeys" value and the
// "destroyed" key in the "metadata" key should be set to true.
func TestKV_Subkeys_Destroyed(t *testing.T) {
	coreConfig := &vault.CoreConfig{
		LogicalBackends: map[string]logical.Factory{
			"kv": logicalKv.VersionedKVFactory,
		},
	}

	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
		HandlerFunc: vaulthttp.Handler,
	})

	cluster.Start()
	defer cluster.Cleanup()

	cores := cluster.Cores

	core := cores[0].Core
	c := cluster.Cores[0].Client
	vault.TestWaitActive(t, core)

	// Mount a KVv2 backend
	err := c.Sys().Mount("kv", &api.MountInput{
		Type: "kv-v2",
	})
	if err != nil {
		t.Fatal(err)
	}

	kvData := map[string]interface{}{
		"data": map[string]interface{}{
			"bar": "a",
		},
	}

	secretRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
		return c.Logical().Write("kv/data/foo", kvData)
	})
	if err != nil {
		t.Fatalf("write failed, err :%v, resp: %#v", err, secretRaw)
	}

	destroyVersions := map[string]interface{}{
		"versions": []int{1},
	}

	secretRaw, err = kvRequestWithRetry(t, func() (interface{}, error) {
		return c.Logical().Write("kv/destroy/foo", destroyVersions)
	})
	if err != nil {
		t.Fatalf("destroy failed, err :%v, resp: %#v", err, secretRaw)
	}

	secret, ok := secretRaw.(*api.Secret)
	if !ok {
		t.Fatalf("response not an api.Secret, actual: %#v", secretRaw)
	}

	apiRespRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
		req := c.NewRequest("GET", "/v1/kv/subkeys/foo")
		return c.RawRequestWithContext(context.Background(), req)
	})

	apiResp, ok := apiRespRaw.(*api.Response)
	if !ok {
		t.Fatalf("response not a api.Response, actual: %#v", apiRespRaw)
	}

	if apiResp != nil {
		defer apiResp.Body.Close()
	}

	if err == nil || apiResp == nil {
		t.Fatalf("expected subkeys request to fail, err :%v, resp: %#v", err, apiResp)
	}

	if apiResp.StatusCode != http.StatusNotFound {
		t.Fatalf("expected subkeys request to fail with %d status code, resp: %#v", http.StatusNotFound, apiResp)
	}

	secret, err = api.ParseSecret(apiResp.Body)
	if err != nil {
		t.Fatalf("failed to parse resp body, err: %v", err)
	}

	subkeys, ok := secret.Data["subkeys"]
	if !ok {
		t.Fatalf("key \"subkeys\" not found in response")
	}

	if subkeys != nil {
		t.Fatalf("expected nil subkeys, actual: %#v", subkeys)
	}

	metadata, ok := secret.Data["metadata"].(map[string]interface{})

	if !ok {
		t.Fatalf("metadata not present in response or invalid, metadata: %#v", secret.Data["metadata"])
	}

	if destroyed, ok := metadata["destroyed"].(bool); !ok || !destroyed {
		t.Fatalf("expected destroyed to be true, metadata: %#v", metadata)
	}
}

// TestKV_Subkeys_CurrentVersion writes multiples versions of a secret to the
// KVv2 secret engine. It ensures that the subkeys endpoint returns a 200 status
// and current version of the secret.
func TestKV_Subkeys_CurrentVersion(t *testing.T) {
	coreConfig := &vault.CoreConfig{
		LogicalBackends: map[string]logical.Factory{
			"kv": logicalKv.VersionedKVFactory,
		},
	}

	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
		HandlerFunc: vaulthttp.Handler,
	})

	cluster.Start()
	defer cluster.Cleanup()

	cores := cluster.Cores

	core := cores[0].Core
	c := cluster.Cores[0].Client
	vault.TestWaitActive(t, core)

	// Mount a KVv2 backend
	err := c.Sys().Mount("kv", &api.MountInput{
		Type: "kv-v2",
	})
	if err != nil {
		t.Fatal(err)
	}

	kvData := map[string]interface{}{
		"data": map[string]interface{}{
			"foo": "does-not-matter",
			"bar": map[string]interface{}{
				"a": map[string]interface{}{
					"c": "does-not-matter",
				},
				"b": map[string]interface{}{},
			},
		},
	}

	secretRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
		return c.Logical().Write("kv/data/foo", kvData)
	})
	if err != nil {
		t.Fatalf("write failed, err :%v, resp: %#v", err, secretRaw)
	}

	kvData = map[string]interface{}{
		"data": map[string]interface{}{
			"baz": "does-not-matter",
		},
	}

	secretRaw, err = kvRequestWithRetry(t, func() (interface{}, error) {
		return c.Logical().JSONMergePatch(context.Background(), "kv/data/foo", kvData)
	})
	if err != nil {
		t.Fatalf("patch failed, err :%v, resp: %#v", err, secretRaw)
	}

	apiRespRaw, err := kvRequestWithRetry(t, func() (interface{}, error) {
		req := c.NewRequest("GET", "/v1/kv/subkeys/foo")
		return c.RawRequestWithContext(context.Background(), req)
	})

	apiResp, ok := apiRespRaw.(*api.Response)
	if !ok {
		t.Fatalf("response not a api.Response, actual: %#v", apiRespRaw)
	}

	if apiResp != nil {
		defer apiResp.Body.Close()
	}

	if err != nil || apiResp == nil {
		t.Fatalf("subkeys request failed, err :%v, resp: %#v", err, apiResp)
	}

	if apiResp.StatusCode != http.StatusOK {
		t.Fatalf("expected subkeys request to succeed with %d status code, resp: %#v", http.StatusOK, apiResp)
	}

	secret, err := api.ParseSecret(apiResp.Body)
	if err != nil {
		t.Fatalf("failed to parse resp body, err: %v", err)
	}

	expectedSubkeys := map[string]interface{}{
		"foo": nil,
		"bar": map[string]interface{}{
			"a": map[string]interface{}{
				"c": nil,
			},
			"b": nil,
		},
		"baz": nil,
	}

	if diff := deep.Equal(secret.Data["subkeys"], expectedSubkeys); len(diff) > 0 {
		t.Fatalf("resp and expected data mismatch, diff: %#v", diff)
	}
}
