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

package database

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"testing"

	"github.com/hashicorp/vault/sdk/helper/base62"
	"github.com/hashicorp/vault/sdk/logical"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

// Test_newClientCertificateGenerator tests the ClientCertificateGenerator struct based on the config
func Test_newClientCertificateGenerator(t *testing.T) {
	type args struct {
		config map[string]interface{}
	}
	tests := []struct {
		name    string
		args    args
		want    ClientCertificateGenerator
		wantErr bool
	}{
		{
			name: "newClientCertificateGenerator with nil config",
			args: args{
				config: nil,
			},
			want: ClientCertificateGenerator{
				CommonNameTemplate: "",
				CAPrivateKey:       "",
				CACert:             "",
				KeyType:            "",
				KeyBits:            0,
				SignatureBits:      0,
			},
		},
		{
			name: "newClientCertificateGenerator with zero value key_type",
			args: args{
				config: map[string]interface{}{
					"key_type": "",
				},
			},
			want: ClientCertificateGenerator{
				KeyType: "",
			},
		},
		{
			name: "newClientCertificateGenerator with rsa value key_type",
			args: args{
				config: map[string]interface{}{
					"key_type": "rsa",
				},
			},
			want: ClientCertificateGenerator{
				KeyType:       "rsa",
				KeyBits:       2048,
				SignatureBits: 256,
			},
		},
		{
			name: "newClientCertificateGenerator with ec value key_type",
			args: args{
				config: map[string]interface{}{
					"key_type": "ec",
				},
			},
			want: ClientCertificateGenerator{
				KeyType:       "ec",
				KeyBits:       256,
				SignatureBits: 256,
			},
		},
		{
			name: "newClientCertificateGenerator with ed25519 value key_type",
			args: args{
				config: map[string]interface{}{
					"key_type": "ed25519",
				},
			},
			want: ClientCertificateGenerator{
				KeyType:       "ed25519",
				SignatureBits: 256,
			},
		},
		{
			name: "newClientCertificateGenerator with invalid key_type",
			args: args{
				config: map[string]interface{}{
					"key_type": "ece",
				},
			},
			wantErr: true,
		},
		{
			name: "newClientCertificateGenerator with zero value key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "0",
				},
			},
			want: ClientCertificateGenerator{
				KeyBits: 0,
			},
		},
		{
			name: "newClientCertificateGenerator with 2048 value key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "2048",
				},
			},
			want: ClientCertificateGenerator{
				KeyBits: 2048,
			},
		},
		{
			name: "newClientCertificateGenerator with 3072 value key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "3072",
				},
			},
			want: ClientCertificateGenerator{
				KeyBits: 3072,
			},
		},
		{
			name: "newClientCertificateGenerator with 4096 value key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "4096",
				},
			},
			want: ClientCertificateGenerator{
				KeyBits: 4096,
			},
		},
		{
			name: "newClientCertificateGenerator with 224 value key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "224",
				},
			},
			want: ClientCertificateGenerator{
				KeyBits: 224,
			},
		},
		{
			name: "newClientCertificateGenerator with 256 value key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "256",
				},
			},
			want: ClientCertificateGenerator{
				KeyBits: 256,
			},
		},
		{
			name: "newClientCertificateGenerator with 384 value key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "384",
				},
			},
			want: ClientCertificateGenerator{
				KeyBits: 384,
			},
		},
		{
			name: "newClientCertificateGenerator with 521 value key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "521",
				},
			},
			want: ClientCertificateGenerator{
				KeyBits: 521,
			},
		},
		{
			name: "newClientCertificateGenerator with invalid key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "4097",
				},
			},
			wantErr: true,
		},
		{
			name: "newClientCertificateGenerator with zero value signature_bits",
			args: args{
				config: map[string]interface{}{
					"signature_bits": "0",
				},
			},
			want: ClientCertificateGenerator{
				SignatureBits: 0,
			},
		},
		{
			name: "newClientCertificateGenerator with 256 value signature_bits",
			args: args{
				config: map[string]interface{}{
					"signature_bits": "256",
				},
			},
			want: ClientCertificateGenerator{
				SignatureBits: 256,
			},
		},
		{
			name: "newClientCertificateGenerator with 384 value signature_bits",
			args: args{
				config: map[string]interface{}{
					"signature_bits": "384",
				},
			},
			want: ClientCertificateGenerator{
				SignatureBits: 384,
			},
		},
		{
			name: "newClientCertificateGenerator with 512 value signature_bits",
			args: args{
				config: map[string]interface{}{
					"signature_bits": "512",
				},
			},
			want: ClientCertificateGenerator{
				SignatureBits: 512,
			},
		},
		{
			name: "newClientCertificateGenerator with invalid signature_bits",
			args: args{
				config: map[string]interface{}{
					"signature_bits": "612",
				},
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := newClientCertificateGenerator(tt.args.config)
			if tt.wantErr {
				assert.Error(t, err)
				return
			}
			assert.Equal(t, tt.want, got)
		})
	}
}

func Test_newPasswordGenerator(t *testing.T) {
	type args struct {
		config map[string]interface{}
	}
	tests := []struct {
		name    string
		args    args
		want    passwordGenerator
		wantErr bool
	}{
		{
			name: "newPasswordGenerator with nil config",
			args: args{
				config: nil,
			},
			want: passwordGenerator{
				PasswordPolicy: "",
			},
		},
		{
			name: "newPasswordGenerator without password_policy",
			args: args{
				config: map[string]interface{}{},
			},
			want: passwordGenerator{
				PasswordPolicy: "",
			},
		},
		{
			name: "newPasswordGenerator with password_policy",
			args: args{
				config: map[string]interface{}{
					"password_policy": "test-policy",
				},
			},
			want: passwordGenerator{
				PasswordPolicy: "test-policy",
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := newPasswordGenerator(tt.args.config)
			if tt.wantErr {
				assert.Error(t, err)
				return
			}
			assert.Equal(t, tt.want, got)
		})
	}
}

func Test_newRSAKeyGenerator(t *testing.T) {
	type args struct {
		config map[string]interface{}
	}
	tests := []struct {
		name    string
		args    args
		want    rsaKeyGenerator
		wantErr bool
	}{
		{
			name: "newRSAKeyGenerator with nil config",
			args: args{
				config: nil,
			},
			want: rsaKeyGenerator{
				Format:  "pkcs8",
				KeyBits: 2048,
			},
		},
		{
			name: "newRSAKeyGenerator with empty config",
			args: args{
				config: map[string]interface{}{},
			},
			want: rsaKeyGenerator{
				Format:  "pkcs8",
				KeyBits: 2048,
			},
		},
		{
			name: "newRSAKeyGenerator with zero value format",
			args: args{
				config: map[string]interface{}{
					"format": "",
				},
			},
			want: rsaKeyGenerator{
				Format:  "pkcs8",
				KeyBits: 2048,
			},
		},
		{
			name: "newRSAKeyGenerator with zero value key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "0",
				},
			},
			want: rsaKeyGenerator{
				Format:  "pkcs8",
				KeyBits: 2048,
			},
		},
		{
			name: "newRSAKeyGenerator with format",
			args: args{
				config: map[string]interface{}{
					"format": "pkcs8",
				},
			},
			want: rsaKeyGenerator{
				Format:  "pkcs8",
				KeyBits: 2048,
			},
		},
		{
			name: "newRSAKeyGenerator with format case insensitive",
			args: args{
				config: map[string]interface{}{
					"format": "PKCS8",
				},
			},
			want: rsaKeyGenerator{
				Format:  "PKCS8",
				KeyBits: 2048,
			},
		},
		{
			name: "newRSAKeyGenerator with 3072 key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "3072",
				},
			},
			want: rsaKeyGenerator{
				Format:  "pkcs8",
				KeyBits: 3072,
			},
		},
		{
			name: "newRSAKeyGenerator with 4096 key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "4096",
				},
			},
			want: rsaKeyGenerator{
				Format:  "pkcs8",
				KeyBits: 4096,
			},
		},
		{
			name: "newRSAKeyGenerator with invalid key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "4097",
				},
			},
			wantErr: true,
		},
		{
			name: "newRSAKeyGenerator with invalid format",
			args: args{
				config: map[string]interface{}{
					"format": "pkcs1",
				},
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := newRSAKeyGenerator(tt.args.config)
			if tt.wantErr {
				assert.Error(t, err)
				return
			}
			assert.Equal(t, tt.want, got)
		})
	}
}

func Test_passwordGenerator_generate(t *testing.T) {
	config := logical.TestBackendConfig()
	b := Backend(config)
	b.Setup(context.Background(), config)

	type args struct {
		config  map[string]interface{}
		mock    func() interface{}
		passGen logical.PasswordGenerator
	}
	tests := []struct {
		name       string
		args       args
		wantRegexp string
		wantErr    bool
	}{
		{
			name: "wrapper missing v4 and v5 interface",
			args: args{
				mock: func() interface{} {
					return nil
				},
			},
			wantErr: true,
		},
		{
			name: "v4: generate password using GenerateCredentials",
			args: args{
				mock: func() interface{} {
					v4Mock := new(mockLegacyDatabase)
					v4Mock.On("GenerateCredentials", mock.Anything).
						Return("v4-generated-password", nil).
						Times(1)
					return v4Mock
				},
			},
			wantRegexp: "^v4-generated-password$",
		},
		{
			name: "v5: generate password without policy",
			args: args{
				mock: func() interface{} {
					return new(mockNewDatabase)
				},
			},
			wantRegexp: "^[a-zA-Z0-9-]{20}$",
		},
		{
			name: "v5: generate password with non-existing policy",
			args: args{
				config: map[string]interface{}{
					"password_policy": "not-created",
				},
				mock: func() interface{} {
					return new(mockNewDatabase)
				},
			},
			wantErr: true,
		},
		{
			name: "v5: generate password with existing policy",
			args: args{
				config: map[string]interface{}{
					"password_policy": "test-policy",
				},
				mock: func() interface{} {
					return new(mockNewDatabase)
				},
				passGen: func() (string, error) {
					return base62.Random(30)
				},
			},
			wantRegexp: "^[a-zA-Z0-9]{30}$",
		},
		{
			name: "v5: generate password with existing policy static",
			args: args{
				config: map[string]interface{}{
					"password_policy": "test-policy",
				},
				mock: func() interface{} {
					return new(mockNewDatabase)
				},
				passGen: func() (string, error) {
					return "policy-generated-password", nil
				},
			},
			wantRegexp: "^policy-generated-password$",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Set up the version wrapper with a mock database implementation
			wrapper := databaseVersionWrapper{}
			switch m := tt.args.mock().(type) {
			case *mockNewDatabase:
				wrapper.v5 = m
			case *mockLegacyDatabase:
				wrapper.v4 = m
			}

			// Set the password policy for the test case
			config.System.(*logical.StaticSystemView).SetPasswordPolicy(
				"test-policy", tt.args.passGen)

			// Generate the password
			pg, err := newPasswordGenerator(tt.args.config)
			got, err := pg.generate(context.Background(), b, wrapper)
			if tt.wantErr {
				assert.Error(t, err)
				return
			}
			assert.Regexp(t, tt.wantRegexp, got)

			// Assert all expected calls took place on the mock
			if m, ok := wrapper.v5.(*mockNewDatabase); ok {
				m.AssertExpectations(t)
			}
			if m, ok := wrapper.v4.(*mockLegacyDatabase); ok {
				m.AssertExpectations(t)
			}
		})
	}
}

func Test_passwordGenerator_configMap(t *testing.T) {
	type args struct {
		config map[string]interface{}
	}
	tests := []struct {
		name string
		args args
		want map[string]interface{}
	}{
		{
			name: "nil config results in empty map",
			args: args{
				config: nil,
			},
			want: map[string]interface{}{},
		},
		{
			name: "empty config results in empty map",
			args: args{
				config: map[string]interface{}{},
			},
			want: map[string]interface{}{},
		},
		{
			name: "input config is equal to output config",
			args: args{
				config: map[string]interface{}{
					"password_policy": "test-policy",
				},
			},
			want: map[string]interface{}{
				"password_policy": "test-policy",
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pg, err := newPasswordGenerator(tt.args.config)
			assert.NoError(t, err)
			cm, err := pg.configMap()
			assert.NoError(t, err)
			assert.Equal(t, tt.want, cm)
		})
	}
}

func Test_rsaKeyGenerator_generate(t *testing.T) {
	type args struct {
		config map[string]interface{}
	}
	tests := []struct {
		name string
		args args
	}{
		{
			name: "generate RSA key with nil default config",
			args: args{
				config: nil,
			},
		},
		{
			name: "generate RSA key with empty default config",
			args: args{
				config: map[string]interface{}{},
			},
		},
		{
			name: "generate RSA key with 2048 key_bits and format",
			args: args{
				config: map[string]interface{}{
					"key_bits": "2048",
					"format":   "pkcs8",
				},
			},
		},
		{
			name: "generate RSA key with 2048 key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "2048",
				},
			},
		},
		{
			name: "generate RSA key with 3072 key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "3072",
				},
			},
		},
		{
			name: "generate RSA key with 4096 key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": "4096",
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Generate the RSA key pair
			kg, err := newRSAKeyGenerator(tt.args.config)
			public, private, err := kg.generate(rand.Reader)
			assert.NoError(t, err)
			assert.NotEmpty(t, public)
			assert.NotEmpty(t, private)

			// Decode the public and private key PEMs
			pubBlock, pubRest := pem.Decode(public)
			privBlock, privRest := pem.Decode(private)
			assert.NotNil(t, pubBlock)
			assert.Empty(t, pubRest)
			assert.Equal(t, "PUBLIC KEY", pubBlock.Type)
			assert.NotNil(t, privBlock)
			assert.Empty(t, privRest)
			assert.Equal(t, "PRIVATE KEY", privBlock.Type)

			// Assert that we can parse the public key PEM block
			pub, err := x509.ParsePKIXPublicKey(pubBlock.Bytes)
			assert.NoError(t, err)
			assert.NotNil(t, pub)
			assert.IsType(t, &rsa.PublicKey{}, pub)

			// Assert that we can parse the private key PEM block in
			// the configured format
			switch kg.Format {
			case "pkcs8":
				priv, err := x509.ParsePKCS8PrivateKey(privBlock.Bytes)
				assert.NoError(t, err)
				assert.NotNil(t, priv)
				assert.IsType(t, &rsa.PrivateKey{}, priv)
			default:
				t.Fatal("unknown format")
			}
		})
	}
}

func Test_rsaKeyGenerator_configMap(t *testing.T) {
	type args struct {
		config map[string]interface{}
	}
	tests := []struct {
		name string
		args args
		want map[string]interface{}
	}{
		{
			name: "nil config results in defaults",
			args: args{
				config: nil,
			},
			want: map[string]interface{}{
				"format":   "pkcs8",
				"key_bits": 2048,
			},
		},
		{
			name: "empty config results in defaults",
			args: args{
				config: map[string]interface{}{},
			},
			want: map[string]interface{}{
				"format":   "pkcs8",
				"key_bits": 2048,
			},
		},
		{
			name: "config with format",
			args: args{
				config: map[string]interface{}{
					"format": "pkcs8",
				},
			},
			want: map[string]interface{}{
				"format":   "pkcs8",
				"key_bits": 2048,
			},
		},
		{
			name: "config with key_bits",
			args: args{
				config: map[string]interface{}{
					"key_bits": 4096,
				},
			},
			want: map[string]interface{}{
				"format":   "pkcs8",
				"key_bits": 4096,
			},
		},
		{
			name: "config with format and key_bits",
			args: args{
				config: map[string]interface{}{
					"format":   "pkcs8",
					"key_bits": 3072,
				},
			},
			want: map[string]interface{}{
				"format":   "pkcs8",
				"key_bits": 3072,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			kg, err := newRSAKeyGenerator(tt.args.config)
			assert.NoError(t, err)
			cm, err := kg.configMap()
			assert.NoError(t, err)
			assert.Equal(t, tt.want, cm)
		})
	}
}
