package secrets

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"strings"
)

// AESGCMCodec seals and opens persisted secret values with one 256-bit key.
// Values use RawStdEncoding(nonce || ciphertext) with no additional data.
type AESGCMCodec struct {
	aead cipher.AEAD
}

// NewAESGCMCodec creates a codec for a 32-byte AES-256-GCM key.
func NewAESGCMCodec(key []byte) (*AESGCMCodec, error) {
	if len(key) != 32 {
		return nil, fmt.Errorf("AES-GCM key must be 32 bytes")
	}
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("create AES cipher: %w", err)
	}
	aead, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("create AES-GCM: %w", err)
	}
	return &AESGCMCodec{aead: aead}, nil
}

// Seal encrypts a secret value for persistence.
func (c *AESGCMCodec) Seal(plaintext string) (string, error) {
	nonce := make([]byte, c.aead.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return "", fmt.Errorf("generate AES-GCM nonce: %w", err)
	}
	sealed := c.aead.Seal(nonce, nonce, []byte(plaintext), nil)
	return base64.RawStdEncoding.EncodeToString(sealed), nil
}

// Open decrypts a persisted secret value.
func (c *AESGCMCodec) Open(encoded string) (string, error) {
	ciphertext, err := base64.RawStdEncoding.DecodeString(strings.TrimSpace(encoded))
	if err != nil {
		return "", fmt.Errorf("decode AES-GCM value: %w", err)
	}
	if len(ciphertext) < c.aead.NonceSize()+c.aead.Overhead() {
		return "", fmt.Errorf("AES-GCM value is invalid")
	}
	nonce := ciphertext[:c.aead.NonceSize()]
	sealed := ciphertext[c.aead.NonceSize():]
	plaintext, err := c.aead.Open(nil, nonce, sealed, nil)
	if err != nil {
		return "", fmt.Errorf("open AES-GCM value: %w", err)
	}
	return string(plaintext), nil
}
