/*
Copyright © The ESO Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package ibm

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"testing"

	sm "github.com/IBM/secrets-manager-go-sdk/v2/secretsmanagerv2"
	"github.com/go-openapi/strfmt"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	utilpointer "k8s.io/utils/ptr"
	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"

	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
	v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
	fakesm "github.com/external-secrets/external-secrets/providers/v1/ibm/fake"
)

const (
	errExpectedErr       = "wanted error got nil"
	secretKey            = "test-secret"
	secretUUID           = "d5deb37a-7883-4fe2-a5e7-3c15420adc76"
	iamCredentialsSecret = "iam_credentials/"
)

type secretManagerTestCase struct {
	name            string
	mockClient      *fakesm.IBMMockClient
	apiInput        *sm.GetSecretOptions
	apiOutput       sm.SecretIntf
	getByNameInput  *sm.GetSecretByNameTypeOptions
	getByNameOutput sm.SecretIntf
	getByNameError  error
	ref             *esv1.ExternalSecretDataRemoteRef
	serviceURL      *string
	apiErr          error
	expectError     string
	expectedSecret  string
	// for testing secretmap
	expectedData map[string][]byte
}

func makeValidSecretManagerTestCase() *secretManagerTestCase {
	smtc := secretManagerTestCase{
		mockClient:      &fakesm.IBMMockClient{},
		apiInput:        makeValidAPIInput(),
		ref:             makeValidRef(),
		apiOutput:       makeValidAPIOutput(),
		getByNameInput:  makeValidGetByNameInput(),
		getByNameOutput: makeValidAPIOutput(),
		getByNameError:  nil,
		serviceURL:      nil,
		apiErr:          nil,
		expectError:     "",
		expectedSecret:  "",
		expectedData:    map[string][]byte{},
	}
	mcParams := fakesm.IBMMockClientParams{
		GetSecretOptions:       smtc.apiInput,
		GetSecretOutput:        smtc.apiOutput,
		GetSecretErr:           smtc.apiErr,
		GetSecretByNameOptions: smtc.getByNameInput,
		GetSecretByNameOutput:  smtc.getByNameOutput,
		GetSecretByNameErr:     smtc.getByNameError,
	}
	smtc.mockClient.WithValue(mcParams)
	return &smtc
}

func makeValidRef() *esv1.ExternalSecretDataRemoteRef {
	return &esv1.ExternalSecretDataRemoteRef{
		Key:     secretUUID,
		Version: "default",
	}
}

func makeValidAPIInput() *sm.GetSecretOptions {
	return &sm.GetSecretOptions{
		ID: new(secretUUID),
	}
}

func makeValidAPIOutput() sm.SecretIntf {
	secret := &sm.Secret{
		SecretType: utilpointer.To(sm.Secret_SecretType_Arbitrary),
		Name:       new("testyname"),
		ID:         new(secretUUID),
	}
	var i sm.SecretIntf = secret
	return i
}

func makeValidGetByNameInput() *sm.GetSecretByNameTypeOptions {
	return &sm.GetSecretByNameTypeOptions{}
}

func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
	smtc := makeValidSecretManagerTestCase()
	for _, fn := range tweaks {
		fn(smtc)
	}
	mcParams := fakesm.IBMMockClientParams{
		GetSecretOptions:       smtc.apiInput,
		GetSecretOutput:        smtc.apiOutput,
		GetSecretErr:           smtc.apiErr,
		GetSecretByNameOptions: smtc.getByNameInput,
		GetSecretByNameOutput:  smtc.getByNameOutput,
		GetSecretByNameErr:     smtc.apiErr,
	}
	smtc.mockClient.WithValue(mcParams)
	return smtc
}

// This case can be shared by both GetSecret and GetSecretMap tests.
// bad case: set apiErr.
var setAPIErr = func(smtc *secretManagerTestCase) {
	smtc.apiErr = errors.New("oh no")
	smtc.expectError = "oh no"
}

var setNilMockClient = func(smtc *secretManagerTestCase) {
	smtc.mockClient = nil
	smtc.expectError = errUninitializedIBMProvider
}

// simple tests for Validate Store.
func TestValidateStore(t *testing.T) {
	p := providerIBM{}
	store := &esv1.SecretStore{
		Spec: esv1.SecretStoreSpec{
			Provider: &esv1.SecretStoreProvider{
				IBM: &esv1.IBMProvider{},
			},
		},
	}
	_, err := p.ValidateStore(store)
	if err == nil {
		t.Error(errExpectedErr)
	} else if err.Error() != "serviceURL is required" {
		t.Errorf("service URL test failed")
	}
	url := "my-url"
	store.Spec.Provider.IBM.ServiceURL = &url
	_, err = p.ValidateStore(store)
	if err == nil {
		t.Error(errExpectedErr)
	} else if err.Error() != "missing auth method" {
		t.Errorf("KeySelector test failed: expected missing auth method, got %v", err)
	}
	ns := "ns-one"
	store.Spec.Provider.IBM.Auth.SecretRef = &esv1.IBMAuthSecretRef{
		SecretAPIKey: v1.SecretKeySelector{
			Name:      "foo",
			Key:       "bar",
			Namespace: &ns,
		},
		IAMEndpoint: "https://iam.cloud.ibm.com",
	}
	_, err = p.ValidateStore(store)
	if err == nil {
		t.Error(errExpectedErr)
	} else if err.Error() != "namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore" {
		t.Errorf("KeySelector test failed: expected namespace not allowed, got %v", err)
	}

	// add container auth test
	store = &esv1.SecretStore{
		Spec: esv1.SecretStoreSpec{
			Provider: &esv1.SecretStoreProvider{
				IBM: &esv1.IBMProvider{
					ServiceURL: &url,
					Auth: esv1.IBMAuth{
						ContainerAuth: &esv1.IBMAuthContainerAuth{
							Profile:       "Trusted IAM Profile",
							TokenLocation: "/a/path/to/nowhere/that/should/exist",
						},
					},
				},
			},
		},
	}
	_, err = p.ValidateStore(store)
	expected := "cannot read container auth token"
	if !ErrorContains(err, expected) {
		t.Errorf("ProfileSelector test failed: %s, expected: '%s'", err.Error(), expected)
	}
}

// test the sm<->gcp interface
// make sure correct values are passed and errors are handled accordingly.
func TestIBMSecretManagerGetSecret(t *testing.T) {
	secretString := "changedvalue"
	secretUsername := "userName"
	secretPassword := "P@ssw0rd"
	secretAPIKey := "01234567890"
	secretCertificate := "certificate_value"
	firstValue := "val1"

	// good case: default version is set
	// key is passed in, output is sent back
	setSecretString := func(smtc *secretManagerTestCase) {
		secret := &sm.ArbitrarySecret{
			SecretType: utilpointer.To(sm.Secret_SecretType_Arbitrary),
			Name:       new("testyname"),
			ID:         new(secretUUID),
			Payload:    &secretString,
		}
		smtc.name = "good case: default version is set"
		smtc.apiOutput = secret
		smtc.apiInput.ID = new(secretUUID)
		smtc.expectedSecret = secretString
	}

	// good case: custom version set
	setCustomKey := func(smtc *secretManagerTestCase) {
		secret := &sm.ArbitrarySecret{
			SecretType: utilpointer.To(sm.Secret_SecretType_Arbitrary),
			Name:       new("testyname"),
			ID:         new(secretUUID),
			Payload:    &secretString,
		}
		smtc.name = "good case: custom version set"
		smtc.ref.Key = "arbitrary/" + secretUUID
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.expectedSecret = secretString
	}

	// bad case: arbitrary type secret which is destroyed
	badArbitSecret := func(smtc *secretManagerTestCase) {
		secret := &sm.ArbitrarySecret{
			SecretType: utilpointer.To(sm.Secret_SecretType_Arbitrary),
			Name:       new("testyname"),
			ID:         new(secretUUID),
		}
		smtc.name = "bad case: arbitrary type without property"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretUUID
		smtc.expectError = "key payload does not exist in secret " + secretUUID
	}

	// bad case: username_password type without property
	secretUserPass := "username_password/" + secretUUID
	badSecretUserPass := func(smtc *secretManagerTestCase) {
		secret := &sm.UsernamePasswordSecret{
			SecretType: utilpointer.To(sm.Secret_SecretType_UsernamePassword),
			Name:       new("testyname"),
			ID:         new(secretUUID),
			Username:   &secretUsername,
			Password:   &secretPassword,
		}
		smtc.name = "bad case: username_password type without property"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretUserPass
		smtc.expectError = "remoteRef.property required for secret type username_password"
	}

	// good case: username_password type with property
	funcSetUserPass := func(secretName, property, name string) func(smtc *secretManagerTestCase) {
		return func(smtc *secretManagerTestCase) {
			secret := &sm.UsernamePasswordSecret{
				SecretType: utilpointer.To(sm.Secret_SecretType_UsernamePassword),
				Name:       new("testyname"),
				ID:         new(secretUUID),
				Username:   &secretUsername,
				Password:   &secretPassword,
			}
			smtc.name = name
			smtc.apiInput.ID = new(secretUUID)
			smtc.apiOutput = secret
			smtc.ref.Key = "username_password/" + secretName
			smtc.ref.Property = property
			if property == "username" {
				smtc.expectedSecret = secretUsername
			} else {
				smtc.expectedSecret = secretPassword
			}
		}
	}
	setSecretUserPassByID := funcSetUserPass(secretUUID, "username", "good case: username_password type - get username by ID")

	// good case: iam_credentials type
	funcSetSecretIam := func(secretName, name string) func(*secretManagerTestCase) {
		return func(smtc *secretManagerTestCase) {
			secret := &sm.IAMCredentialsSecret{
				SecretType: utilpointer.To(sm.Secret_SecretType_IamCredentials),
				Name:       new("testyname"),
				ID:         new(secretUUID),
				ApiKey:     new(secretAPIKey),
			}
			smtc.apiInput.ID = new(secretUUID)
			smtc.name = name
			smtc.apiOutput = secret
			smtc.ref.Key = iamCredentialsSecret + secretName
			smtc.expectedSecret = secretAPIKey
		}
	}

	setSecretIamByID := funcSetSecretIam(secretUUID, "good case: iam_credenatials type - get API Key by ID")

	// good case: iam_credentials type - get API Key by name, providing the secret group ID
	funcSetSecretIamNew := func(secretName, groupName, name string) func(*secretManagerTestCase) {
		return func(smtc *secretManagerTestCase) {
			secret := &sm.IAMCredentialsSecret{
				SecretType: utilpointer.To(sm.Secret_SecretType_IamCredentials),
				Name:       new("testyname"),
				ID:         new(secretUUID),
				ApiKey:     new(secretAPIKey),
			}
			smtc.getByNameInput.Name = &secretName
			smtc.getByNameInput.SecretGroupName = &groupName
			smtc.getByNameInput.SecretType = utilpointer.To(sm.Secret_SecretType_IamCredentials)

			smtc.name = name
			smtc.getByNameOutput = secret
			smtc.ref.Key = groupName + "/" + iamCredentialsSecret + secretName
			smtc.expectedSecret = secretAPIKey
		}
	}
	setSecretIamByNameNew := funcSetSecretIamNew("testyname", "testGroup", "good case: iam_credenatials type - get API Key by name - new mechanism")

	// good case: service_credentials type
	dummySrvCreds := &sm.ServiceCredentialsSecretCredentials{
		Apikey: &secretAPIKey,
	}

	funcSetSecretSrvCred := func(secretName, name string) func(*secretManagerTestCase) {
		return func(smtc *secretManagerTestCase) {
			secret := &sm.ServiceCredentialsSecret{
				SecretType:  utilpointer.To(sm.Secret_SecretType_ServiceCredentials),
				Name:        new("testyname"),
				ID:          new(secretUUID),
				Credentials: dummySrvCreds,
			}
			smtc.apiInput.ID = new(secretUUID)
			smtc.name = name
			smtc.apiOutput = secret
			smtc.ref.Key = "service_credentials/" + secretName
			smtc.expectedSecret = "{\"apikey\":\"01234567890\"}"
		}
	}

	setSecretSrvCredByID := funcSetSecretSrvCred(secretUUID, "good case: service_credentials type - get creds by ID")

	funcSetCertSecretTest := func(secret sm.SecretIntf, name, certType string, good bool) func(*secretManagerTestCase) {
		return func(smtc *secretManagerTestCase) {
			smtc.name = name
			smtc.apiInput.ID = new(secretUUID)
			smtc.apiOutput = secret
			smtc.ref.Key = certType + "/" + secretUUID
			if good {
				smtc.ref.Property = "certificate"
				smtc.expectedSecret = secretCertificate
			} else {
				smtc.expectError = "remoteRef.property required for secret type " + certType
			}
		}
	}

	// good case: imported_cert type with property
	importedCert := &sm.ImportedCertificate{
		SecretType:   utilpointer.To(sm.Secret_SecretType_ImportedCert),
		Name:         new("testyname"),
		ID:           new(secretUUID),
		Certificate:  new(secretCertificate),
		Intermediate: new("intermediate"),
		PrivateKey:   new("private_key"),
	}
	setSecretCert := funcSetCertSecretTest(importedCert, "good case: imported_cert type with property", sm.Secret_SecretType_ImportedCert, true)

	// good case: imported_cert type without a private_key
	importedCertNoPvtKey := func(smtc *secretManagerTestCase) {
		secret := &sm.ImportedCertificate{
			SecretType:  utilpointer.To(sm.Secret_SecretType_ImportedCert),
			Name:        new("testyname"),
			ID:          new(secretUUID),
			Certificate: new(secretCertificate),
		}
		smtc.name = "good case: imported cert without private key"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = "imported_cert/" + secretUUID
		smtc.ref.Property = "private_key"
		smtc.expectedSecret = ""
	}

	// bad case: imported_cert type without property
	badSecretCert := funcSetCertSecretTest(importedCert, "bad case: imported_cert type without property", sm.Secret_SecretType_ImportedCert, false)

	// good case: public_cert type with property
	publicCert := &sm.PublicCertificate{
		SecretType:   utilpointer.To(sm.Secret_SecretType_PublicCert),
		Name:         new("testyname"),
		ID:           new(secretUUID),
		Certificate:  new(secretCertificate),
		Intermediate: new("intermediate"),
		PrivateKey:   new("private_key"),
	}
	setSecretPublicCert := funcSetCertSecretTest(publicCert, "good case: public_cert type with property", sm.Secret_SecretType_PublicCert, true)

	// bad case: public_cert type without property
	badSecretPublicCert := funcSetCertSecretTest(publicCert, "bad case: public_cert type without property", sm.Secret_SecretType_PublicCert, false)

	// good case: private_cert type with property
	privateCert := &sm.PrivateCertificate{
		SecretType:  utilpointer.To(sm.Secret_SecretType_PublicCert),
		Name:        new("testyname"),
		ID:          new(secretUUID),
		Certificate: new(secretCertificate),
		PrivateKey:  new("private_key"),
	}
	setSecretPrivateCert := funcSetCertSecretTest(privateCert, "good case: private_cert type with property", sm.Secret_SecretType_PrivateCert, true)

	// bad case: private_cert type without property
	badSecretPrivateCert := funcSetCertSecretTest(privateCert, "bad case: private_cert type without property", sm.Secret_SecretType_PrivateCert, false)

	secretDataKV := make(map[string]any)
	secretDataKV["key1"] = firstValue

	secretDataKVComplex := make(map[string]any)
	secretKVComplex := `{"key1":"val1","key2":"val2","key3":"val3","keyC":{"keyC1":"valC1","keyC2":"valC2"},"special.log":"file-content"}`
	json.Unmarshal([]byte(secretKVComplex), &secretDataKVComplex)

	secretKV := "kv/" + secretUUID

	// bad case: kv type with key which is not in payload
	badSecretKV := func(smtc *secretManagerTestCase) {
		secret := &sm.KVSecret{
			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
			Name:       new("testyname"),
			ID:         new(secretUUID),
			Data:       secretDataKV,
		}
		smtc.name = "bad case: kv type with key which is not in payload"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretKV
		smtc.ref.Property = "other-key"
		smtc.expectError = "key other-key does not exist in secret kv/" + secretUUID
	}

	// good case: kv type with property
	setSecretKV := func(smtc *secretManagerTestCase) {
		secret := &sm.KVSecret{
			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
			Name:       new("testyname"),
			ID:         new(secretUUID),
			Data:       secretDataKV,
		}
		smtc.name = "good case: kv type with property"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretKV
		smtc.ref.Property = "key1"
		smtc.expectedSecret = firstValue
	}

	// good case: kv type with property, returns specific value
	setSecretKVWithKey := func(smtc *secretManagerTestCase) {
		secret := &sm.KVSecret{
			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
			Name:       new("testyname"),
			ID:         new(secretUUID),
			Data:       secretDataKVComplex,
		}
		smtc.name = "good case: kv type with property, returns specific value"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretKV
		smtc.ref.Property = "key2"
		smtc.expectedSecret = "val2"
	}

	// good case: kv type with property and path, returns specific value
	setSecretKVWithKeyPath := func(smtc *secretManagerTestCase) {
		secret := &sm.KVSecret{
			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
			Name:       new("testyname"),
			ID:         new(secretUUID),
			Data:       secretDataKVComplex,
		}
		smtc.name = "good case: kv type with property and path, returns specific value"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretKV
		smtc.ref.Property = "keyC.keyC2"
		smtc.expectedSecret = "valC2"
	}

	// good case: kv type with property and dot, returns specific value
	setSecretKVWithKeyDot := func(smtc *secretManagerTestCase) {
		secret := &sm.KVSecret{
			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
			Name:       new("testyname"),
			ID:         new(secretUUID),
			Data:       secretDataKVComplex,
		}
		smtc.name = "good case: kv type with property and dot, returns specific value"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretKV
		smtc.ref.Property = "special.log"
		smtc.expectedSecret = "file-content"
	}

	// good case: kv type without property, returns all
	setSecretKVWithOutKey := func(smtc *secretManagerTestCase) {
		secret := &sm.KVSecret{
			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
			Name:       new("testyname"),
			ID:         new(secretUUID),
			Data:       secretDataKVComplex,
		}
		smtc.name = "good case: kv type without property, returns all"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretKV
		smtc.expectedSecret = secretKVComplex
	}

	customCredentialsSecretCredentialsContent := make(map[string]any)
	customCredentialsSecretCredentialsContent["key1"] = firstValue

	customCredentialsSecretCredentialsContentComplex := make(map[string]any)
	customCredentialsSecretComplex := `{"key1":"val1","key2":"val2","key3":"val3","keyC":{"keyC1":"valC1","keyC2":"valC2"},"special.log":"file-content"}`
	json.Unmarshal([]byte(customCredentialsSecretComplex), &customCredentialsSecretCredentialsContentComplex)

	secretCustomCredentials := "custom_credentials/" + secretUUID

	// bad case: custom credentials type with key which is not in payload
	badSecretCustomCredentials := func(smtc *secretManagerTestCase) {
		secret := &sm.CustomCredentialsSecret{
			SecretType:         utilpointer.To(sm.Secret_SecretType_CustomCredentials),
			Name:               new("testyname"),
			ID:                 new(secretUUID),
			CredentialsContent: customCredentialsSecretCredentialsContent,
		}
		smtc.name = "bad case: custom credentials type with key which is not in payload"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretCustomCredentials
		smtc.ref.Property = "other-key"
		smtc.expectError = "key other-key does not exist in secret custom_credentials/" + secretUUID
	}

	// good case: custom credentials type with property
	setSecretCustomCredentials := func(smtc *secretManagerTestCase) {
		secret := &sm.CustomCredentialsSecret{
			SecretType:         utilpointer.To(sm.Secret_SecretType_CustomCredentials),
			Name:               new("testyname"),
			ID:                 new(secretUUID),
			CredentialsContent: customCredentialsSecretCredentialsContent,
		}
		smtc.name = "good case: custom_credentials type with property"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretCustomCredentials
		smtc.ref.Property = "key1"
		smtc.expectedSecret = firstValue
	}

	// good case: custom_credentials type with property, returns specific value
	setSecretCustomCredentialsWithKey := func(smtc *secretManagerTestCase) {
		secret := &sm.CustomCredentialsSecret{
			SecretType:         utilpointer.To(sm.Secret_SecretType_CustomCredentials),
			Name:               new("testyname"),
			ID:                 new(secretUUID),
			CredentialsContent: customCredentialsSecretCredentialsContentComplex,
		}
		smtc.name = "good case: custom_credentials type with property, returns specific value"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretCustomCredentials
		smtc.ref.Property = "key2"
		smtc.expectedSecret = "val2"
	}

	// good case: custom_credentials type with property and path, returns specific value
	setSecretCustomCredentialsWithKeyPath := func(smtc *secretManagerTestCase) {
		secret := &sm.CustomCredentialsSecret{
			SecretType:         utilpointer.To(sm.Secret_SecretType_CustomCredentials),
			Name:               new("testyname"),
			ID:                 new(secretUUID),
			CredentialsContent: customCredentialsSecretCredentialsContentComplex,
		}
		smtc.name = "good case: custom_credentials type with property and path, returns specific value"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretCustomCredentials
		smtc.ref.Property = "keyC.keyC2"
		smtc.expectedSecret = "valC2"
	}

	// good case: custom_credentials type with property and dot, returns specific value
	setSecretCustomCredentialsWithKeyDot := func(smtc *secretManagerTestCase) {
		secret := &sm.CustomCredentialsSecret{
			SecretType:         utilpointer.To(sm.Secret_SecretType_CustomCredentials),
			Name:               new("testyname"),
			ID:                 new(secretUUID),
			CredentialsContent: customCredentialsSecretCredentialsContentComplex,
		}
		smtc.name = "good case: custom_credentials type with property and dot, returns specific value"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretCustomCredentials
		smtc.ref.Property = "special.log"
		smtc.expectedSecret = "file-content"
	}

	// good case: custom_credentials type without property, returns all
	setSecretCustomCredentialsWithOutKey := func(smtc *secretManagerTestCase) {
		secret := &sm.CustomCredentialsSecret{
			SecretType:         utilpointer.To(sm.Secret_SecretType_CustomCredentials),
			Name:               new("testyname"),
			ID:                 new(secretUUID),
			CredentialsContent: customCredentialsSecretCredentialsContentComplex,
		}
		smtc.name = "good case: custom_credentials type without property, returns all"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretCustomCredentials
		smtc.expectedSecret = customCredentialsSecretComplex
	}

	successCases := []*secretManagerTestCase{
		makeValidSecretManagerTestCaseCustom(setSecretString),
		makeValidSecretManagerTestCaseCustom(setCustomKey),
		makeValidSecretManagerTestCaseCustom(badArbitSecret),
		makeValidSecretManagerTestCaseCustom(setAPIErr),
		makeValidSecretManagerTestCaseCustom(setNilMockClient),
		makeValidSecretManagerTestCaseCustom(badSecretUserPass),
		makeValidSecretManagerTestCaseCustom(setSecretUserPassByID),
		makeValidSecretManagerTestCaseCustom(setSecretIamByID),
		makeValidSecretManagerTestCaseCustom(setSecretCert),
		makeValidSecretManagerTestCaseCustom(setSecretKV),
		makeValidSecretManagerTestCaseCustom(setSecretKVWithKey),
		makeValidSecretManagerTestCaseCustom(setSecretKVWithKeyPath),
		makeValidSecretManagerTestCaseCustom(setSecretKVWithKeyDot),
		makeValidSecretManagerTestCaseCustom(setSecretKVWithOutKey),
		makeValidSecretManagerTestCaseCustom(badSecretKV),
		makeValidSecretManagerTestCaseCustom(badSecretCert),
		makeValidSecretManagerTestCaseCustom(importedCertNoPvtKey),
		makeValidSecretManagerTestCaseCustom(setSecretPublicCert),
		makeValidSecretManagerTestCaseCustom(badSecretPublicCert),
		makeValidSecretManagerTestCaseCustom(setSecretPrivateCert),
		makeValidSecretManagerTestCaseCustom(badSecretPrivateCert),
		makeValidSecretManagerTestCaseCustom(setSecretIamByNameNew),
		makeValidSecretManagerTestCaseCustom(setSecretSrvCredByID),
		makeValidSecretManagerTestCaseCustom(setSecretCustomCredentials),
		makeValidSecretManagerTestCaseCustom(setSecretCustomCredentialsWithKey),
		makeValidSecretManagerTestCaseCustom(setSecretCustomCredentialsWithKeyPath),
		makeValidSecretManagerTestCaseCustom(setSecretCustomCredentialsWithKeyDot),
		makeValidSecretManagerTestCaseCustom(setSecretCustomCredentialsWithOutKey),
		makeValidSecretManagerTestCaseCustom(badSecretCustomCredentials),
	}

	sm := providerIBM{}
	for _, v := range successCases {
		t.Run(v.name, func(t *testing.T) {
			sm.IBMClient = v.mockClient
			out, err := sm.GetSecret(context.Background(), *v.ref)
			if !ErrorContains(err, v.expectError) {
				t.Errorf("unexpected error:\n%s, expected:\n'%s'", err.Error(), v.expectError)
			}
			if string(out) != v.expectedSecret {
				t.Errorf("unexpected secret: expected:\n%s\ngot:\n%s", v.expectedSecret, string(out))
			}
		})
	}
}

func TestGetSecretMap(t *testing.T) {
	secretUsername := "user1"
	secretPassword := "P@ssw0rd"
	secretAPIKey := "01234567890"
	nilValue := "<nil>"
	secretCertificate := "certificate_value"
	secretPrivateKey := "private_key_value"
	secretIntermediate := "intermediate_value"
	timeValue := "0001-01-01T00:00:00.000Z"

	secretComplex := map[string]any{
		"key1": "val1",
		"key2": "val2",
		"keyC": map[string]any{
			"keyC1": map[string]string{
				"keyA": "valA",
				"keyB": "valB",
			},
		},
	}

	dummySrvCreds := &sm.ServiceCredentialsSecretCredentials{
		Apikey: &secretAPIKey,
	}

	// good case: arbitrary
	setArbitrary := func(smtc *secretManagerTestCase) {
		payload := `{"foo":"bar"}`
		secret := &sm.ArbitrarySecret{
			Name:       new("testyname"),
			ID:         new(secretUUID),
			SecretType: utilpointer.To(sm.Secret_SecretType_Arbitrary),
			Payload:    &payload,
		}
		smtc.name = "good case: arbitrary"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretUUID
		smtc.expectedData["arbitrary"] = []byte(payload)
	}

	// good case: username_password
	setSecretUserPass := func(smtc *secretManagerTestCase) {
		secret := &sm.UsernamePasswordSecret{
			Name:       new("testyname"),
			ID:         new(secretUUID),
			SecretType: utilpointer.To(sm.Secret_SecretType_UsernamePassword),
			Username:   &secretUsername,
			Password:   &secretPassword,
		}
		smtc.name = "good case: username_password"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = "username_password/" + secretUUID
		smtc.expectedData["username"] = []byte(secretUsername)
		smtc.expectedData["password"] = []byte(secretPassword)
	}

	// good case: iam_credentials
	setSecretIam := func(smtc *secretManagerTestCase) {
		secret := &sm.IAMCredentialsSecret{
			Name:       new("testyname"),
			ID:         new(secretUUID),
			SecretType: utilpointer.To(sm.Secret_SecretType_IamCredentials),
			ApiKey:     new(secretAPIKey),
		}
		smtc.name = "good case: iam_credentials"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = iamCredentialsSecret + secretUUID
		smtc.expectedData["apikey"] = []byte(secretAPIKey)
	}

	// good case: iam_credentials by name using new mechanism
	setSecretIamByName := func(smtc *secretManagerTestCase) {
		secret := &sm.IAMCredentialsSecret{
			Name:       new("testyname"),
			ID:         new(secretUUID),
			SecretType: utilpointer.To(sm.Secret_SecretType_IamCredentials),
			ApiKey:     new(secretAPIKey),
		}
		smtc.name = "good case: iam_credentials by name using new mechanism"
		smtc.getByNameInput.Name = new("testyname")
		smtc.getByNameInput.SecretGroupName = new("groupName")
		smtc.getByNameInput.SecretType = utilpointer.To(sm.Secret_SecretType_IamCredentials)

		smtc.getByNameOutput = secret
		smtc.apiOutput = secret
		smtc.ref.Key = "groupName/" + iamCredentialsSecret + "testyname"
		smtc.expectedData["apikey"] = []byte(secretAPIKey)
	}

	// bad case: iam_credentials of a destroyed secret
	badSecretIam := func(smtc *secretManagerTestCase) {
		secret := &sm.IAMCredentialsSecret{
			Name:       new("testyname"),
			ID:         new(secretUUID),
			SecretType: utilpointer.To(sm.Secret_SecretType_IamCredentials),
		}
		smtc.name = "bad case: iam_credentials of a destroyed secret"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = iamCredentialsSecret + secretUUID
		smtc.expectError = "key api_key does not exist in secret " + secretUUID
	}

	funcCertTest := func(secret sm.SecretIntf, name, certType string) func(*secretManagerTestCase) {
		return func(smtc *secretManagerTestCase) {
			smtc.name = name
			smtc.apiInput.ID = new(secretUUID)
			smtc.apiOutput = secret
			smtc.ref.Key = certType + "/" + secretUUID
			smtc.expectedData["certificate"] = []byte(secretCertificate)
			smtc.expectedData["private_key"] = []byte(secretPrivateKey)
			smtc.expectedData["intermediate"] = []byte(secretIntermediate)
		}
	}

	// good case: service_credentials
	setSecretSrvCreds := func(smtc *secretManagerTestCase) {
		secret := &sm.ServiceCredentialsSecret{
			Name:        new("testyname"),
			ID:          new(secretUUID),
			SecretType:  utilpointer.To(sm.Secret_SecretType_IamCredentials),
			Credentials: dummySrvCreds,
		}
		smtc.name = "good case: service_credentials"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = "service_credentials/" + secretUUID
		smtc.expectedData["credentials"] = fmt.Appendf(nil, "%+v", map[string]string{"apikey": secretAPIKey})
	}

	// good case: imported_cert
	importedCert := &sm.ImportedCertificate{
		SecretType:   utilpointer.To(sm.Secret_SecretType_ImportedCert),
		Name:         new("testyname"),
		ID:           new(secretUUID),
		Certificate:  new(secretCertificate),
		Intermediate: new(secretIntermediate),
		PrivateKey:   new(secretPrivateKey),
	}
	setSecretCert := funcCertTest(importedCert, "good case: imported_cert", sm.Secret_SecretType_ImportedCert)

	// good case: public_cert
	publicCert := &sm.PublicCertificate{
		SecretType:   utilpointer.To(sm.Secret_SecretType_PublicCert),
		Name:         new("testyname"),
		ID:           new(secretUUID),
		Certificate:  new(secretCertificate),
		Intermediate: new(secretIntermediate),
		PrivateKey:   new(secretPrivateKey),
	}
	setSecretPublicCert := funcCertTest(publicCert, "good case: public_cert", sm.Secret_SecretType_PublicCert)

	// good case: private_cert
	setSecretPrivateCert := func(smtc *secretManagerTestCase) {
		secret := &sm.PrivateCertificate{
			Name:        new("testyname"),
			ID:          new(secretUUID),
			SecretType:  utilpointer.To(sm.Secret_SecretType_PrivateCert),
			Certificate: &secretCertificate,
			PrivateKey:  &secretPrivateKey,
		}
		smtc.name = "good case: private_cert"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = "private_cert/" + secretUUID
		smtc.expectedData["certificate"] = []byte(secretCertificate)
		smtc.expectedData["private_key"] = []byte(secretPrivateKey)
	}

	// good case: arbitrary with metadata
	setArbitraryWithMetadata := func(smtc *secretManagerTestCase) {
		payload := `{"foo":"bar"}`
		secret := &sm.ArbitrarySecret{
			CreatedBy:  new("testCreatedBy"),
			CreatedAt:  &strfmt.DateTime{},
			Downloaded: new(false),
			Labels:     []string{"abc", "def", "xyz"},
			LocksTotal: new(int64(20)),
			Payload:    &payload,
		}
		smtc.name = "good case: arbitrary with metadata"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretUUID
		smtc.ref.MetadataPolicy = esv1.ExternalSecretMetadataPolicyFetch
		smtc.expectedData = map[string][]byte{
			"arbitrary":       []byte(payload),
			"created_at":      []byte(timeValue),
			"created_by":      []byte(*secret.CreatedBy),
			"crn":             []byte(nilValue),
			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
			"id":              []byte(nilValue),
			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
			"payload":         []byte(payload),
			"secret_group_id": []byte(nilValue),
			"secret_type":     []byte(nilValue),
			"updated_at":      []byte(nilValue),
			"versions_total":  []byte(nilValue),
		}
	}

	// good case: iam_credentials with metadata
	setSecretIamWithMetadata := func(smtc *secretManagerTestCase) {
		secret := &sm.IAMCredentialsSecret{
			CreatedBy:  new("testCreatedBy"),
			CreatedAt:  &strfmt.DateTime{},
			Downloaded: new(false),
			Labels:     []string{"abc", "def", "xyz"},
			LocksTotal: new(int64(20)),
			ApiKey:     new(secretAPIKey),
		}
		smtc.name = "good case: iam_credentials with metadata"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = iamCredentialsSecret + secretUUID
		smtc.ref.MetadataPolicy = esv1.ExternalSecretMetadataPolicyFetch
		smtc.expectedData = map[string][]byte{
			"api_key":         []byte(secretAPIKey),
			"apikey":          []byte(secretAPIKey),
			"created_at":      []byte(timeValue),
			"created_by":      []byte(*secret.CreatedBy),
			"crn":             []byte(nilValue),
			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
			"id":              []byte(nilValue),
			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
			"reuse_api_key":   []byte(nilValue),
			"secret_group_id": []byte(nilValue),
			"secret_type":     []byte(nilValue),
			"ttl":             []byte(nilValue),
			"updated_at":      []byte(nilValue),
			"versions_total":  []byte(nilValue),
		}
	}

	// "good case: username_password with metadata
	setSecretUserPassWithMetadata := func(smtc *secretManagerTestCase) {
		secret := &sm.UsernamePasswordSecret{
			CreatedBy:  new("testCreatedBy"),
			CreatedAt:  &strfmt.DateTime{},
			Downloaded: new(false),
			Labels:     []string{"abc", "def", "xyz"},
			LocksTotal: new(int64(20)),
			Username:   &secretUsername,
			Password:   &secretPassword,
		}
		smtc.name = "good case: username_password with metadata"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = "username_password/" + secretUUID
		smtc.expectedData["username"] = []byte(secretUsername)
		smtc.expectedData["password"] = []byte(secretPassword)
		smtc.ref.MetadataPolicy = esv1.ExternalSecretMetadataPolicyFetch
		smtc.expectedData = map[string][]byte{
			"created_at":      []byte(timeValue),
			"created_by":      []byte(*secret.CreatedBy),
			"crn":             []byte(nilValue),
			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
			"id":              []byte(nilValue),
			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
			"password":        []byte(secretPassword),
			"rotation":        []byte(nilValue),
			"secret_group_id": []byte(nilValue),
			"secret_type":     []byte(nilValue),
			"updated_at":      []byte(nilValue),
			"username":        []byte(secretUsername),
			"versions_total":  []byte(nilValue),
		}
	}

	// good case: imported_cert with metadata
	setimportedCertWithMetadata := func(smtc *secretManagerTestCase) {
		secret := &sm.ImportedCertificate{
			CreatedBy:    new("testCreatedBy"),
			CreatedAt:    &strfmt.DateTime{},
			Downloaded:   new(false),
			Labels:       []string{"abc", "def", "xyz"},
			LocksTotal:   new(int64(20)),
			Certificate:  new(secretCertificate),
			Intermediate: new(secretIntermediate),
			PrivateKey:   new(secretPrivateKey),
		}
		smtc.name = "good case: imported_cert with metadata"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = "imported_cert" + "/" + secretUUID

		smtc.ref.MetadataPolicy = esv1.ExternalSecretMetadataPolicyFetch
		smtc.expectedData = map[string][]byte{
			"certificate":     []byte(secretCertificate),
			"created_at":      []byte(timeValue),
			"created_by":      []byte(*secret.CreatedBy),
			"crn":             []byte(nilValue),
			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
			"id":              []byte(nilValue),
			"intermediate":    []byte(secretIntermediate),
			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
			"private_key":     []byte(secretPrivateKey),
			"secret_group_id": []byte(nilValue),
			"secret_type":     []byte(nilValue),
			"updated_at":      []byte(nilValue),
			"versions_total":  []byte(nilValue),
		}
	}

	// good case: imported_cert without a private_key
	setimportedCertWithNoPvtKey := func(smtc *secretManagerTestCase) {
		secret := &sm.ImportedCertificate{
			CreatedBy:    new("testCreatedBy"),
			CreatedAt:    &strfmt.DateTime{},
			Downloaded:   new(false),
			Labels:       []string{"abc", "def", "xyz"},
			LocksTotal:   new(int64(20)),
			Certificate:  new(secretCertificate),
			Intermediate: new(secretIntermediate),
		}
		smtc.name = "good case: imported_cert without private key"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = "imported_cert/" + secretUUID

		smtc.expectedData = map[string][]byte{
			"certificate":  []byte(secretCertificate),
			"intermediate": []byte(secretIntermediate),
			"private_key":  []byte(""),
		}
	}

	// good case: public_cert with metadata
	setPublicCertWithMetadata := func(smtc *secretManagerTestCase) {
		secret := &sm.PublicCertificate{
			CreatedBy:    new("testCreatedBy"),
			CreatedAt:    &strfmt.DateTime{},
			Downloaded:   new(false),
			Labels:       []string{"abc", "def", "xyz"},
			LocksTotal:   new(int64(20)),
			Certificate:  new(secretCertificate),
			Intermediate: new(secretIntermediate),
			PrivateKey:   new(secretPrivateKey),
		}
		smtc.name = "good case: public_cert with metadata"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = "public_cert" + "/" + secretUUID

		smtc.ref.MetadataPolicy = esv1.ExternalSecretMetadataPolicyFetch
		smtc.expectedData = map[string][]byte{
			"certificate":     []byte(secretCertificate),
			"created_at":      []byte(timeValue),
			"created_by":      []byte(*secret.CreatedBy),
			"crn":             []byte(nilValue),
			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
			"id":              []byte(nilValue),
			"intermediate":    []byte(secretIntermediate),
			"key_algorithm":   []byte(nilValue),
			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
			"private_key":     []byte(secretPrivateKey),
			"rotation":        []byte(nilValue),
			"secret_group_id": []byte(nilValue),
			"secret_type":     []byte(nilValue),
			"updated_at":      []byte(nilValue),
			"versions_total":  []byte(nilValue),
		}
	}

	// good case: private_cert with metadata
	setPrivateCertWithMetadata := func(smtc *secretManagerTestCase) {
		expirationDate := &strfmt.DateTime{}
		secret := &sm.PrivateCertificate{
			CreatedBy:      new("testCreatedBy"),
			CreatedAt:      &strfmt.DateTime{},
			Downloaded:     new(false),
			Labels:         []string{"abc", "def", "xyz"},
			LocksTotal:     new(int64(20)),
			Certificate:    new(secretCertificate),
			PrivateKey:     new(secretPrivateKey),
			ExpirationDate: expirationDate,
		}
		smtc.name = "good case: private_cert with metadata"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = "private_cert" + "/" + secretUUID
		smtc.ref.MetadataPolicy = esv1.ExternalSecretMetadataPolicyFetch
		smtc.expectedData = map[string][]byte{
			"certificate":          []byte(secretCertificate),
			"certificate_template": []byte(nilValue),
			"common_name":          []byte(nilValue),
			"created_at":           []byte(timeValue),
			"created_by":           []byte(*secret.CreatedBy),
			"crn":                  []byte(nilValue),
			"downloaded":           []byte(strconv.FormatBool(*secret.Downloaded)),
			"expiration_date":      []byte(expirationDate.String()),
			"id":                   []byte(nilValue),
			"issuer":               []byte(nilValue),
			"labels":               []byte("[" + strings.Join(secret.Labels, " ") + "]"),
			"locks_total":          []byte(strconv.Itoa(int(*secret.LocksTotal))),
			"private_key":          []byte(secretPrivateKey),
			"secret_group_id":      []byte(nilValue),
			"secret_type":          []byte(nilValue),
			"serial_number":        []byte(nilValue),
			"signing_algorithm":    []byte(nilValue),
			"updated_at":           []byte(nilValue),
			"validity":             []byte(nilValue),
			"versions_total":       []byte(nilValue),
		}
	}

	// good case: kv with property and metadata
	setSecretKVWithMetadata := func(smtc *secretManagerTestCase) {
		secret := &sm.KVSecret{
			CreatedBy:  new("testCreatedBy"),
			CreatedAt:  &strfmt.DateTime{},
			Downloaded: new(false),
			Labels:     []string{"abc", "def", "xyz"},
			LocksTotal: new(int64(20)),
			Data:       secretComplex,
		}
		smtc.name = "good case: kv, with property and with metadata"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = "kv/" + secretUUID
		smtc.ref.MetadataPolicy = esv1.ExternalSecretMetadataPolicyFetch
		smtc.expectedData = map[string][]byte{
			"created_at":      []byte(timeValue),
			"created_by":      []byte(*secret.CreatedBy),
			"crn":             []byte(nilValue),
			"data":            []byte("map[key1:val1 key2:val2 keyC:map[keyC1:map[keyA:valA keyB:valB]]]"),
			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
			"id":              []byte(nilValue),
			"key1":            []byte("val1"),
			"key2":            []byte("val2"),
			"keyC":            []byte(`{"keyC1":{"keyA":"valA","keyB":"valB"}}`),
			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
			"secret_group_id": []byte(nilValue),
			"secret_type":     []byte(nilValue),
			"updated_at":      []byte(nilValue),
			"versions_total":  []byte(nilValue),
		}
	}

	// good case: custom_credentials with property and metadata
	setSecretCustomCredentialsWithMetadata := func(smtc *secretManagerTestCase) {
		secret := &sm.CustomCredentialsSecret{
			CreatedBy:          new("testCreatedBy"),
			CreatedAt:          &strfmt.DateTime{},
			Downloaded:         new(false),
			Labels:             []string{"abc", "def", "xyz"},
			LocksTotal:         new(int64(20)),
			CredentialsContent: secretComplex,
		}
		smtc.name = "good case: custom_credentials, with property and with metadata"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = "custom_credentials/" + secretUUID
		smtc.ref.MetadataPolicy = esv1.ExternalSecretMetadataPolicyFetch
		smtc.expectedData = map[string][]byte{
			"created_at":          []byte(timeValue),
			"created_by":          []byte(*secret.CreatedBy),
			"crn":                 []byte(nilValue),
			"credentials_content": []byte("map[key1:val1 key2:val2 keyC:map[keyC1:map[keyA:valA keyB:valB]]]"),
			"downloaded":          []byte(strconv.FormatBool(*secret.Downloaded)),
			"id":                  []byte(nilValue),
			"key1":                []byte("val1"),
			"key2":                []byte("val2"),
			"keyC":                []byte(`{"keyC1":{"keyA":"valA","keyB":"valB"}}`),
			"labels":              []byte("[" + strings.Join(secret.Labels, " ") + "]"),
			"locks_total":         []byte(strconv.Itoa(int(*secret.LocksTotal))),
			"secret_group_id":     []byte(nilValue),
			"secret_type":         []byte(nilValue),
			"updated_at":          []byte(nilValue),
			"versions_total":      []byte(nilValue),
			"configuration":       []byte(nilValue),
		}
	}

	// good case: iam_credentials without metadata
	setSecretIamWithoutMetadata := func(smtc *secretManagerTestCase) {
		secret := &sm.IAMCredentialsSecret{
			CreatedBy:  new("testCreatedBy"),
			CreatedAt:  &strfmt.DateTime{},
			Downloaded: new(false),
			Labels:     []string{"abc", "def", "xyz"},
			LocksTotal: new(int64(20)),
			ApiKey:     new(secretAPIKey),
		}
		smtc.name = "good case: iam_credentials without metadata"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = iamCredentialsSecret + secretUUID
		smtc.ref.MetadataPolicy = esv1.ExternalSecretMetadataPolicyNone
		smtc.expectedData = map[string][]byte{
			"apikey": []byte(secretAPIKey),
		}
	}

	secretKeyKV := "kv/" + secretUUID
	// good case: kv, no property, return entire payload as key:value pairs
	setSecretKV := func(smtc *secretManagerTestCase) {
		secret := &sm.KVSecret{
			Name:       new("testyname"),
			ID:         new(secretUUID),
			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
			Data:       secretComplex,
		}
		smtc.name = "good case: kv, no property, return entire payload as key:value pairs"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretKeyKV
		smtc.expectedData["key1"] = []byte("val1")
		smtc.expectedData["key2"] = []byte("val2")
		smtc.expectedData["keyC"] = []byte(`{"keyC1":{"keyA":"valA","keyB":"valB"}}`)
	}

	// good case: kv, with property
	setSecretKVWithProperty := func(smtc *secretManagerTestCase) {
		secret := &sm.KVSecret{
			Name:       new("d5deb37a-7883-4fe2-a5e7-3c15420adc76"),
			ID:         new(secretUUID),
			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
			Data:       secretComplex,
		}
		smtc.name = "good case: kv, with property"
		smtc.apiInput.ID = new(secretUUID)
		smtc.ref.Property = "keyC"
		smtc.apiOutput = secret
		smtc.ref.Key = secretKeyKV
		smtc.expectedData["keyC1"] = []byte(`{"keyA":"valA","keyB":"valB"}`)
	}

	// good case: kv, with property and path
	setSecretKVWithPathAndProperty := func(smtc *secretManagerTestCase) {
		secret := &sm.KVSecret{
			Name:       new(secretUUID),
			ID:         new(secretUUID),
			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
			Data:       secretComplex,
		}
		smtc.name = "good case: kv, with property and path"
		smtc.apiInput.ID = new(secretUUID)
		smtc.ref.Property = "keyC.keyC1"
		smtc.apiOutput = secret
		smtc.ref.Key = secretKeyKV
		smtc.expectedData["keyA"] = []byte("valA")
		smtc.expectedData["keyB"] = []byte("valB")
	}

	// bad case: kv, with property and path
	badSecretKVWithUnknownProperty := func(smtc *secretManagerTestCase) {
		secret := &sm.KVSecret{
			Name:       new("testyname"),
			ID:         new(secretUUID),
			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
			Data:       secretComplex,
		}
		smtc.name = "bad case: kv, with property and path"
		smtc.apiInput.ID = new(secretUUID)
		smtc.ref.Property = "unknown.property"
		smtc.apiOutput = secret
		smtc.ref.Key = secretKeyKV
		smtc.expectError = "key unknown.property does not exist in secret " + secretKeyKV
	}

	secretKeyCustomCredentials := "custom_credentials/" + secretUUID
	// good case: custom_credentials, no property, return entire payload as key:value pairs
	setSecretCustomCredentials := func(smtc *secretManagerTestCase) {
		secret := &sm.CustomCredentialsSecret{
			Name:               new("testyname"),
			ID:                 new(secretUUID),
			SecretType:         utilpointer.To(sm.Secret_SecretType_CustomCredentials),
			CredentialsContent: secretComplex,
		}
		smtc.name = "good case: custom_credentials, no property, return entire payload as key:value pairs"
		smtc.apiInput.ID = new(secretUUID)
		smtc.apiOutput = secret
		smtc.ref.Key = secretKeyCustomCredentials
		smtc.expectedData["key1"] = []byte("val1")
		smtc.expectedData["key2"] = []byte("val2")
		smtc.expectedData["keyC"] = []byte(`{"keyC1":{"keyA":"valA","keyB":"valB"}}`)
	}

	// good case: custom_credentials, with property
	setSecretCustomCredentialsWithProperty := func(smtc *secretManagerTestCase) {
		secret := &sm.CustomCredentialsSecret{
			Name:               new("d5deb37a-7883-4fe2-a5e7-3c15420adc76"),
			ID:                 new(secretUUID),
			SecretType:         utilpointer.To(sm.Secret_SecretType_CustomCredentials),
			CredentialsContent: secretComplex,
		}
		smtc.name = "good case: custom_credentials, with property"
		smtc.apiInput.ID = new(secretUUID)
		smtc.ref.Property = "keyC"
		smtc.apiOutput = secret
		smtc.ref.Key = secretKeyCustomCredentials
		smtc.expectedData["keyC1"] = []byte(`{"keyA":"valA","keyB":"valB"}`)
	}

	// good case: custom_credentials, with property and path
	setSecretCustomCredentialsWithPathAndProperty := func(smtc *secretManagerTestCase) {
		secret := &sm.CustomCredentialsSecret{
			Name:               new(secretUUID),
			ID:                 new(secretUUID),
			SecretType:         utilpointer.To(sm.Secret_SecretType_CustomCredentials),
			CredentialsContent: secretComplex,
		}
		smtc.name = "good case: custom_credentials, with property and path"
		smtc.apiInput.ID = new(secretUUID)
		smtc.ref.Property = "keyC.keyC1"
		smtc.apiOutput = secret
		smtc.ref.Key = secretKeyCustomCredentials
		smtc.expectedData["keyA"] = []byte("valA")
		smtc.expectedData["keyB"] = []byte("valB")
	}

	// bad case: custom_credentials, with property and path
	badSecretCustomCredentialsWithUnknownProperty := func(smtc *secretManagerTestCase) {
		secret := &sm.CustomCredentialsSecret{
			Name:               new("testyname"),
			ID:                 new(secretUUID),
			SecretType:         utilpointer.To(sm.Secret_SecretType_CustomCredentials),
			CredentialsContent: secretComplex,
		}
		smtc.name = "bad case: custom_credentials, with property and path"
		smtc.apiInput.ID = new(secretUUID)
		smtc.ref.Property = "unknown.property"
		smtc.apiOutput = secret
		smtc.ref.Key = secretKeyCustomCredentials
		smtc.expectError = "key unknown.property does not exist in secret " + secretKeyCustomCredentials
	}

	successCases := []*secretManagerTestCase{
		makeValidSecretManagerTestCaseCustom(badSecretIam),
		makeValidSecretManagerTestCaseCustom(setSecretSrvCreds),
		makeValidSecretManagerTestCaseCustom(setArbitrary),
		makeValidSecretManagerTestCaseCustom(setNilMockClient),
		makeValidSecretManagerTestCaseCustom(setAPIErr),
		makeValidSecretManagerTestCaseCustom(setSecretUserPass),
		makeValidSecretManagerTestCaseCustom(setSecretIam),
		makeValidSecretManagerTestCaseCustom(setSecretCert),
		makeValidSecretManagerTestCaseCustom(setSecretKV),
		makeValidSecretManagerTestCaseCustom(setSecretKVWithProperty),
		makeValidSecretManagerTestCaseCustom(setSecretKVWithPathAndProperty),
		makeValidSecretManagerTestCaseCustom(badSecretKVWithUnknownProperty),
		makeValidSecretManagerTestCaseCustom(setSecretCustomCredentials),
		makeValidSecretManagerTestCaseCustom(setSecretCustomCredentialsWithProperty),
		makeValidSecretManagerTestCaseCustom(setSecretCustomCredentialsWithPathAndProperty),
		makeValidSecretManagerTestCaseCustom(badSecretCustomCredentialsWithUnknownProperty),
		makeValidSecretManagerTestCaseCustom(setSecretPublicCert),
		makeValidSecretManagerTestCaseCustom(setSecretPrivateCert),
		makeValidSecretManagerTestCaseCustom(setimportedCertWithNoPvtKey),
		makeValidSecretManagerTestCaseCustom(setSecretIamWithMetadata),
		makeValidSecretManagerTestCaseCustom(setArbitraryWithMetadata),
		makeValidSecretManagerTestCaseCustom(setSecretUserPassWithMetadata),
		makeValidSecretManagerTestCaseCustom(setimportedCertWithMetadata),
		makeValidSecretManagerTestCaseCustom(setPublicCertWithMetadata),
		makeValidSecretManagerTestCaseCustom(setPrivateCertWithMetadata),
		makeValidSecretManagerTestCaseCustom(setSecretKVWithMetadata),
		makeValidSecretManagerTestCaseCustom(setSecretCustomCredentialsWithMetadata),
		makeValidSecretManagerTestCaseCustom(setSecretIamWithoutMetadata),
		makeValidSecretManagerTestCaseCustom(setSecretIamByName),
		makeValidSecretManagerTestCaseCustom(setSecretCustomCredentials),
	}

	sm := providerIBM{}
	for _, v := range successCases {
		t.Run(v.name, func(t *testing.T) {
			sm.IBMClient = v.mockClient
			out, err := sm.GetSecretMap(context.Background(), *v.ref)
			if !ErrorContains(err, v.expectError) {
				t.Errorf("unexpected error: %s, expected: '%s'", err.Error(), v.expectError)
			}
			if err == nil && !reflect.DeepEqual(out, v.expectedData) {
				// Add some debug logs because t.Errorf is hard to debug at a glance.
				for k, val := range out {
					if val2, ok := v.expectedData[k]; !ok {
						t.Logf("%s was not in expected data\n", k)
					} else if !bytes.Equal(val2, val) {
						fmt.Printf("%s did not equal expected value: %s\n", string(val), string(val2))
					}
				}

				for k := range v.expectedData {
					if _, ok := out[k]; !ok {
						t.Logf("%s expected key was not in out\n", k)
					}
				}
				t.Errorf("unexpected secret data: expected:\n%+v\ngot:\n%+v", v.expectedData, out)
			}
		})
	}
}

func TestValidRetryInput(t *testing.T) {
	sm := providerIBM{}

	invalid := "Invalid"
	serviceURL := "http://fake-service-url.cool"

	spec := &esv1.SecretStore{
		Spec: esv1.SecretStoreSpec{
			Provider: &esv1.SecretStoreProvider{
				IBM: &esv1.IBMProvider{
					Auth: esv1.IBMAuth{
						SecretRef: &esv1.IBMAuthSecretRef{
							SecretAPIKey: v1.SecretKeySelector{
								Name: "fake-secret",
								Key:  "fake-key",
							},
						},
					},
					ServiceURL: &serviceURL,
				},
			},
			RetrySettings: &esv1.SecretStoreRetrySettings{
				RetryInterval: &invalid,
			},
		},
	}

	expected := fmt.Sprintf("cannot setup new ibm client: time: invalid duration %q", invalid)
	ctx := context.TODO()
	kube := clientfake.NewClientBuilder().WithObjects(&corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "fake-secret",
			Namespace: "default",
		},
		Data: map[string][]byte{
			"fake-key": []byte("ImAFakeApiKey"),
		},
	}).Build()

	_, err := sm.NewClient(ctx, spec, kube, "default")

	if !ErrorContains(err, expected) {
		t.Errorf("CheckValidRetryInput unexpected error: %s, expected: '%s'", err.Error(), expected)
	}
}

func ErrorContains(out error, want string) bool {
	if out == nil {
		return want == ""
	}
	if want == "" {
		return false
	}
	return strings.Contains(out.Error(), want)
}
