// Package secrets 提供对称加密，用于把敏感配置（首要是 LLM api_key）加密后落库 / 过总线。
//
// 设计：
//   - AES-256-GCM。密钥由环境变量 SUNDYNIX_SECRET_KEY 经 SHA-256 派生为 32 字节（AEAD 同时保证机密性与完整性）。
//   - 密文格式 "enc:1:" + base64url(nonce || ciphertext)，自带版本前缀便于日后轮换算法。
//   - 向后兼容：Decrypt 遇到无前缀的值（历史明文行 / 新填的明文）原样返回，不报错——
//     于是 PG 里的旧明文 key 仍可用，下一次保存即升级为密文。
//
// 全链路约定：网关保存时 Encrypt 落库；密文经 DB 读出后原样过 NATS；消费方（dispatcher/mcp-go）
// 在 bus 层 Decrypt 还原。因此 api_key 在「磁盘」与「线缆」上都不再是明文，仅在真正构建 LLM
// 客户端的内存中短暂还原。各服务必须配置相同的 SUNDYNIX_SECRET_KEY。
package secrets

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"io"
	"log"
	"os"
	"strings"
	"sync"
)

// cipherPrefix 标记一个值是本包产出的密文（含版本号，便于日后算法轮换）。
const cipherPrefix = "enc:1:"

// devDefaultKey 是未设置 SUNDYNIX_SECRET_KEY 时的开发兜底（与 JWT 开发默认对称，便于本地各服务互通）。
// 生产环境必须显式设置 SUNDYNIX_SECRET_KEY，否则加密形同虚设。
const devDefaultKey = "sundynix-dev-secret-change-me"

var (
	gcmOnce sync.Once
	gcm     cipher.AEAD
	gcmErr  error
)

// aead 惰性构建并缓存 AES-256-GCM 实例（密钥来自环境变量，进程内固定）。
func aead() (cipher.AEAD, error) {
	gcmOnce.Do(func() {
		raw := os.Getenv("SUNDYNIX_SECRET_KEY")
		if raw == "" {
			raw = devDefaultKey
			log.Printf("[secrets] SUNDYNIX_SECRET_KEY 未设置，使用开发默认密钥（生产环境务必设置！）")
		}
		sum := sha256.Sum256([]byte(raw)) // 任意长度口令 → 固定 32 字节 AES-256 密钥
		block, err := aes.NewCipher(sum[:])
		if err != nil {
			gcmErr = err
			return
		}
		gcm, gcmErr = cipher.NewGCM(block)
	})
	return gcm, gcmErr
}

// Encrypt 加密明文，返回带前缀的密文；空串原样返回（无需加密）。
func Encrypt(plain string) (string, error) {
	if plain == "" {
		return "", nil
	}
	a, err := aead()
	if err != nil {
		return "", err
	}
	nonce := make([]byte, a.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", err
	}
	ct := a.Seal(nonce, nonce, []byte(plain), nil) // 输出 = nonce || 密文+tag
	return cipherPrefix + base64.RawURLEncoding.EncodeToString(ct), nil
}

// Decrypt 还原密文。无 "enc:" 前缀的值视为历史明文，原样返回（平滑迁移）。
func Decrypt(stored string) (string, error) {
	if !IsEncrypted(stored) {
		return stored, nil // 历史明文 / 用户刚填的明文，直接用
	}
	a, err := aead()
	if err != nil {
		return "", err
	}
	raw, err := base64.RawURLEncoding.DecodeString(stored[len(cipherPrefix):])
	if err != nil {
		return "", err
	}
	ns := a.NonceSize()
	if len(raw) < ns {
		return "", errors.New("secrets: 密文长度不足")
	}
	plain, err := a.Open(nil, raw[:ns], raw[ns:], nil)
	if err != nil {
		return "", err // 密钥不匹配 / 密文被篡改
	}
	return string(plain), nil
}

// IsEncrypted 报告一个值是否为本包产出的密文。
func IsEncrypted(s string) bool {
	return len(s) > len(cipherPrefix) && s[:len(cipherPrefix)] == cipherPrefix
}

// MustHaveKeyInProd 在生产模式(APP_ENV=production/prod 或 GIN_MODE=release)下，
// 若未设置 SUNDYNIX_SECRET_KEY 则直接 fatal——杜绝用开发默认密钥加密（形同明文）。
// 各处理 api_key 的服务（gateway/dispatcher/mcp-go）应在启动时调用；且必须配置相同的密钥。
func MustHaveKeyInProd() {
	if os.Getenv("SUNDYNIX_SECRET_KEY") != "" {
		return
	}
	env := strings.ToLower(os.Getenv("APP_ENV"))
	if env == "production" || env == "prod" || strings.ToLower(os.Getenv("GIN_MODE")) == "release" {
		log.Fatal("[secrets] 生产模式必须设置 SUNDYNIX_SECRET_KEY（32+ 字节强随机），且各服务一致；拒绝以开发默认密钥加密")
	}
}
