// Package auth 提供无状态鉴权能力：JWT 签发/校验 + 密码哈希。
package auth

import (
	"errors"
	"log"
	"os"
	"strings"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"golang.org/x/crypto/bcrypt"
)

// TokenTTL 是访问令牌有效期。
const TokenTTL = 24 * time.Hour

const devSecret = "sundynix-dev-secret-change-me"

// secret 是 JWT 签名密钥。生产必须经 JWT_SECRET 注入强密钥；
// 生产模式(APP_ENV=production/prod 或 GIN_MODE=release)下未设则直接 fatal，杜绝可伪造令牌。
var secret = []byte(resolveSecret())

func resolveSecret() string {
	if s := os.Getenv("JWT_SECRET"); s != "" {
		return s
	}
	if isProd() {
		log.Fatal("[auth] 生产模式必须设置 JWT_SECRET（强随机密钥），拒绝使用开发默认值")
	}
	log.Println("[auth] ⚠️ 使用开发默认 JWT 密钥，生产务必设置 JWT_SECRET")
	return devSecret
}

// isProd 判定是否生产环境。
func isProd() bool {
	env := strings.ToLower(os.Getenv("APP_ENV"))
	return env == "production" || env == "prod" || strings.ToLower(os.Getenv("GIN_MODE")) == "release"
}

// ErrInvalidToken 表示令牌无效/过期/签名不符。
var ErrInvalidToken = errors.New("invalid token")

// Issue 为某用户签发 JWT（subject = userID）。
func Issue(userID string) (string, error) { return issue(userID, TokenTTL) }

func issue(userID string, ttl time.Duration) (string, error) {
	now := time.Now()
	claims := jwt.RegisteredClaims{
		Subject:   userID,
		IssuedAt:  jwt.NewNumericDate(now),
		ExpiresAt: jwt.NewNumericDate(now.Add(ttl)),
	}
	return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secret)
}

// Parse 校验 JWT 并返回 userID（subject）。无效/过期/签名不符返回 ErrInvalidToken。
func Parse(token string) (string, error) {
	t, err := jwt.ParseWithClaims(token, &jwt.RegisteredClaims{}, func(t *jwt.Token) (any, error) {
		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { // 仅接受 HMAC，防 alg 混淆攻击
			return nil, ErrInvalidToken
		}
		return secret, nil
	})
	if err != nil || !t.Valid {
		return "", ErrInvalidToken
	}
	c, ok := t.Claims.(*jwt.RegisteredClaims)
	if !ok || c.Subject == "" {
		return "", ErrInvalidToken
	}
	return c.Subject, nil
}

// HashPassword 用 bcrypt 哈希明文密码。
func HashPassword(pw string) (string, error) {
	b, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
	return string(b), err
}

// CheckPassword 校验明文与 bcrypt 哈希是否匹配。
func CheckPassword(hash, pw string) bool {
	return bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw)) == nil
}

func envOr(k, def string) string {
	if v := os.Getenv(k); v != "" {
		return v
	}
	return def
}
