// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: BUSL-1.1

package database

import (
	"context"
	"os"
	"strings"
	"testing"

	"github.com/hashicorp/vault/helper/namespace"
	postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
	"github.com/hashicorp/vault/sdk/logical"
)

// TestRotateRootPassword_Postgres tests database secrets root rotation for password credentials with Postgres.
func TestRotateRootPassword_Postgres(t *testing.T) {
	cleanup, connURL := postgreshelper.PrepareTestContainer(t)
	defer cleanup()

	cluster, sys := getClusterWithFactory(t, Factory)
	defer cluster.Cleanup()

	connURL = strings.Replace(connURL, "postgres:secret", "{{username}}:{{password}}", 1)

	testRotateRoot(t, sys, true, connURL, "postgresql-database-plugin", "postgres", "secret")
}

// TestRotateRootKeypair_Snowflake_Acc tests database secrets root rotation for private key credentials with Snowflake.
func TestRotateRootKeypair_Snowflake_Acc(t *testing.T) {
	// SNOWFLAKE_PRIVATE_KEY is the path to the private key file.
	privateKeyPath, ok := os.LookupEnv("VAULT_SNOWFLAKE_PRIVATE_KEY")
	if !ok {
		t.Skip("VAULT_SNOWFLAKE_PRIVATE_KEY not set, skipping test")
	}

	keyFile, err := os.ReadFile(privateKeyPath)
	if err != nil {
		t.Fatalf("failed to read private key file: %s", err)
	}

	connURL, ok := os.LookupEnv("VAULT_SNOWFLAKE_CONNECTION_URL")
	if !ok {
		t.Skip("VAULT_SNOWFLAKE_CONNECTION_URL not set, skipping test")
	}

	username, ok := os.LookupEnv("VAULT_SNOWFLAKE_USERNAME")
	if !ok {
		t.Skip("VAULT_SNOWFLAKE_USERNAME not set, skipping test")
	}

	cluster, sys := getClusterWithFactory(t, Factory)
	defer cluster.Cleanup()

	testRotateRoot(t, sys, false, connURL, "snowflake-database-plugin", username, string(keyFile))
}

// Helper function to run rotate root tests.
func testRotateRoot(t *testing.T, sys logical.SystemView, isPassword bool, connURL string, pluginName, username, credential string) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	config.System = sys

	lb, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}
	b, ok := lb.(*databaseBackend)
	if !ok {
		t.Fatal("could not convert to db backend")
	}
	defer b.Cleanup(context.Background())

	// Configure a connection
	configData := map[string]any{
		"connection_url":    connURL,
		"plugin_name":       pluginName,
		"verify_connection": false,
		"allowed_roles":     []string{"*"},
		"name":              "plugin-test",
		"username":          username,
	}
	if isPassword {
		configData["password"] = credential
	} else {
		configData["private_key"] = credential
	}
	req := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "config/plugin-test",
		Storage:   config.StorageView,
		Data:      configData,
	}
	resp, err := b.HandleRequest(namespace.RootContext(context.Background()), req)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("err:%s resp:%#v\n", err, resp)
	}

	// Create a dynamic role
	req = &logical.Request{
		Operation: logical.CreateOperation,
		Path:      "roles/test",
		Storage:   config.StorageView,
		Data: map[string]interface{}{
			"db_name":             "plugin-test",
			"creation_statements": `CREATE USER "{{name}}"`,
		},
	}
	resp, err = b.HandleRequest(context.Background(), req)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("err:%s resp:%#v\n", err, resp)
	}

	// Read some creds to validate the connection
	req = &logical.Request{
		Operation: logical.ReadOperation,
		Path:      "creds/test",
		Storage:   config.StorageView,
	}
	resp, err = b.HandleRequest(context.Background(), req)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("err:%s resp:%#v\n", err, resp)
	}

	// Rotate the root credentials
	req = &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "rotate-root/plugin-test",
		Storage:   config.StorageView,
	}
	resp, err = b.HandleRequest(namespace.RootContext(context.Background()), req)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("err:%s resp:%#v\n", err, resp)
	}

	// Read creds a second time to validate the root rotation still works
	req = &logical.Request{
		Operation: logical.ReadOperation,
		Path:      "creds/test",
		Storage:   config.StorageView,
	}
	resp, err = b.HandleRequest(context.Background(), req)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("err:%s resp:%#v\n", err, resp)
	}

	// Write back the original credential to ensure it no longer works
	req = &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "config/plugin-test",
		Storage:   config.StorageView,
		Data:      configData,
	}
	resp, err = b.HandleRequest(namespace.RootContext(context.Background()), req)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("err:%s resp:%#v\n", err, resp)
	}

	// Read some more creds again and expect an error]
	// Note: For Snowflake, this step may fail if you are using the account's 2nd private key
	req = &logical.Request{
		Operation: logical.ReadOperation,
		Path:      "creds/test",
		Storage:   config.StorageView,
	}
	resp, err = b.HandleRequest(context.Background(), req)
	if err == nil || (resp != nil && !resp.IsError()) {
		t.Fatalf("expected authentication error but did not receive an error, resp:%#v\n", resp)
	}
}
