package namedcredential

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"os"
)

// envelopeVersion is the on-disk format version byte. v1 = AES-256-GCM with
// 12-byte nonce, master key from env var NEXUSCRM_NC_MASTER_KEY (32-byte
// base64-encoded). Future versions (HSM, KMS, key-rotation envelope) will
// bump the version byte and may chain to a key-id table.
const envelopeVersion byte = 0x01

// MasterKeyEnvVar names the env var that holds the AES-256 master key for
// _System_NamedCredentialSecret envelope encryption. Value must be a
// 32-byte key encoded as standard base64 (44 chars including padding).
const MasterKeyEnvVar = "NEXUSCRM_NC_MASTER_KEY"

// ErrMasterKeyMissing is returned when MasterKeyEnvVar is unset.
var ErrMasterKeyMissing = errors.New("named credential master key not configured")

// ErrCiphertextCorrupt is returned when stored ciphertext fails to authenticate
// (wrong key, tampered bytes, truncated envelope, unknown version).
var ErrCiphertextCorrupt = errors.New("named credential ciphertext corrupt or wrong key")

// SecretCipher encrypts/decrypts SecretValue plaintext using AES-256-GCM with
// a master key loaded from MasterKeyEnvVar. Construction fails fast if the
// env var is unset or malformed — startup-time validation is preferred over
// per-write surprises.
type SecretCipher struct {
	aead cipher.AEAD
}

// NewSecretCipher loads the master key from MasterKeyEnvVar and constructs
// an AES-256-GCM cipher. Returns ErrMasterKeyMissing if the var is unset.
func NewSecretCipher() (*SecretCipher, error) {
	raw := os.Getenv(MasterKeyEnvVar)
	if raw == "" {
		return nil, ErrMasterKeyMissing
	}
	return newSecretCipherFromBase64(raw)
}

// NewSecretCipherFromKey constructs a cipher from a raw 32-byte key. Intended
// for tests with known keys; production code uses NewSecretCipher.
func NewSecretCipherFromKey(key []byte) (*SecretCipher, error) {
	if len(key) != 32 {
		return nil, fmt.Errorf("master key must be 32 bytes, got %d", len(key))
	}
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("aes.NewCipher: %w", err)
	}
	aead, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("cipher.NewGCM: %w", err)
	}
	return &SecretCipher{aead: aead}, nil
}

func newSecretCipherFromBase64(b64 string) (*SecretCipher, error) {
	key, err := base64.StdEncoding.DecodeString(b64)
	if err != nil {
		return nil, fmt.Errorf("master key base64 decode: %w", err)
	}
	return NewSecretCipherFromKey(key)
}

// Encrypt produces a versioned envelope:
//
//	<version=0x01> | <nonce 12B> | <ciphertext+gcmTag>
//
// Returned as standard base64 for storage in a TEXT column.
func (c *SecretCipher) Encrypt(plaintext string) (string, error) {
	nonce := make([]byte, c.aead.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", fmt.Errorf("read random nonce: %w", err)
	}
	sealed := c.aead.Seal(nil, nonce, []byte(plaintext), nil)
	envelope := make([]byte, 0, 1+len(nonce)+len(sealed))
	envelope = append(envelope, envelopeVersion)
	envelope = append(envelope, nonce...)
	envelope = append(envelope, sealed...)
	return base64.StdEncoding.EncodeToString(envelope), nil
}

// Decrypt parses a versioned envelope and authenticates+decrypts it.
// Returns ErrCiphertextCorrupt if version is unknown, length is wrong, or
// the GCM tag does not authenticate.
func (c *SecretCipher) Decrypt(stored string) (string, error) {
	envelope, err := base64.StdEncoding.DecodeString(stored)
	if err != nil {
		return "", ErrCiphertextCorrupt
	}
	nonceSize := c.aead.NonceSize()
	if len(envelope) < 1+nonceSize+c.aead.Overhead() {
		return "", ErrCiphertextCorrupt
	}
	if envelope[0] != envelopeVersion {
		return "", ErrCiphertextCorrupt
	}
	nonce := envelope[1 : 1+nonceSize]
	sealed := envelope[1+nonceSize:]
	plaintext, err := c.aead.Open(nil, nonce, sealed, nil)
	if err != nil {
		return "", ErrCiphertextCorrupt
	}
	return string(plaintext), nil
}
