//    \\ SPIKE: Secure your secrets with SPIFFE. — https://spike.ist/
//  \\\\\ Copyright 2024-present SPIKE contributors.
// \\\\\\\ SPDX-License-Identifier: Apache-2.0

package persist

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"

	"github.com/spiffe/spike-sdk-go/config/env"
	"github.com/spiffe/spike-sdk-go/crypto"
	sdkErrors "github.com/spiffe/spike-sdk-go/errors"
	"github.com/spiffe/spike-sdk-go/log"
	"github.com/spiffe/spike-sdk-go/security/mem"
)

func createCipher() cipher.AEAD {
	key := make([]byte, crypto.AES256KeySize) // AES-256 key
	if _, randErr := rand.Read(key); randErr != nil {
		log.FatalLn("createCipher", "message",
			"Failed to generate test key", "err", randErr)
	}

	block, cipherErr := aes.NewCipher(key)
	if cipherErr != nil {
		log.FatalLn("createCipher", "message",
			"Failed to create cipher", "err", cipherErr)
	}

	gcm, gcmErr := cipher.NewGCM(block)
	if gcmErr != nil {
		log.FatalLn("createCipher", "message",
			"Failed to create GCM", "err", gcmErr)
	}

	return gcm
}

// InitializeBackend creates and returns a backend storage implementation based
// on the configured store type in the environment. The function is thread-safe
// through a mutex lock.
//
// Parameters:
//   - rootKey: The encryption key used for backend initialization (used by
//     SQLite backend)
//
// Returns:
//   - A backend.Backend interface implementation:
//   - memory.Store for 'memory' store type or unknown types
//   - SQLite backend for 'sqlite' store type
//
// The actual backend type is determined by env.BackendStoreType():
//   - env.Memory: Returns a no-op memory store
//   - env.Sqlite: Initializes and returns a SQLite backend
//   - default: Falls back to a no-op memory store
//
// The function is safe for concurrent access as it uses a mutex to protect the
// initialization process.
//
// Note: This function modifies the package-level be variable. Later calls
// will reinitialize the backend, potentially losing any existing state.
func InitializeBackend(rootKey *[crypto.AES256KeySize]byte) {
	const fName = "InitializeBackend"

	// Root key is not needed, nor used in in-memory stores.
	// For in-memory stores, ensure that it is always nil, as the alternative
	// might mean a logic, or initialization-flow bug, and an unnecessary
	// crypto material in the memory.
	// In other store types, ensure it is set for security.
	if env.BackendStoreTypeVal() == env.Memory {
		if rootKey != nil {
			failErr := *sdkErrors.ErrRootKeyNotEmpty.Clone()
			failErr.Msg = "root key should be nil for memory store type"
			log.FatalErr(fName, failErr)
		}
	} else {
		if rootKey == nil {
			failErr := *sdkErrors.ErrRootKeyEmpty.Clone()
			failErr.Msg = "root key cannot be nil"
			log.FatalErr(fName, failErr)
		}

		if mem.Zeroed32(rootKey) {
			failErr := *sdkErrors.ErrRootKeyEmpty.Clone()
			failErr.Msg = "root key cannot be empty"
			log.FatalErr(fName, failErr)
		}
	}

	backendMu.Lock()
	defer backendMu.Unlock()

	storeType := env.BackendStoreTypeVal()

	switch storeType {
	case env.Lite:
		be = initializeLiteBackend(rootKey)
	case env.Memory:
		be = initializeInMemoryBackend()
	case env.Sqlite:
		be = initializeSqliteBackend(rootKey)
	default:
		be = initializeInMemoryBackend()
	}

	// Store the backend atomically for safe concurrent access.
	backendPtr.Store(&be)
}
