package middleware

import (
	"encoding/json"
	"net/http"
	"strings"
	"time"

	jwtUtil "sundynix-micro-go/common/utils/jwt"

	jwtv5 "github.com/golang-jwt/jwt/v5"
	"github.com/zeromicro/go-zero/core/logx"
)

// RefreshTokenHeader 续期后新 Token 放在此响应头里，前端读取后静默替换
const RefreshTokenHeader = "X-Refresh-Token"

// AuthMiddleware 网关鉴权 + 自动续期中间件
type AuthMiddleware struct {
	jwtSecret string
	whitelist map[string]bool
}

func NewAuthMiddleware(jwtSecret string, whitelist []string) *AuthMiddleware {
	wl := make(map[string]bool, len(whitelist))
	for _, p := range whitelist {
		wl[p] = true
	}
	return &AuthMiddleware{
		jwtSecret: jwtSecret,
		whitelist: wl,
	}
}

func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// OPTIONS 预检直接放行
		if r.Method == http.MethodOptions {
			next(w, r)
			return
		}

		// 白名单路径放行（支持精确匹配和 /* 前缀通配）
		if m.isWhitelisted(r.URL.Path) {
			next(w, r)
			return
		}

		// 解析 Authorization 头
		authHeader := r.Header.Get("Authorization")
		if authHeader == "" {
			writeUnauthorized(w, "缺少 Authorization 请求头")
			return
		}
		tokenStr := jwtUtil.GetTokenFromHeader(authHeader)
		if tokenStr == "" {
			writeUnauthorized(w, "Token 格式错误")
			return
		}

		j := jwtUtil.NewJWT(m.jwtSecret)
		claims, err := j.ParseToken(tokenStr)
		if err != nil {
			logx.Infof("[zero-gateway] JWT 解析失败: %v, path: %s", err, r.URL.Path)
			writeUnauthorized(w, err.Error())
			return
		}

		// 将用户信息透传到上游，避免上游重复解析 JWT
		r.Header.Set("X-User-Id", claims.BaseClaims.ID)
		r.Header.Set("X-User-Account", claims.BaseClaims.Account)

		// ---- 滑动窗口续期 ----
		// 剩余有效时间 < BufferTime（存储在 token claims 里），说明进入缓冲窗口
		if newToken, ok := m.tryRefresh(j, claims); ok {
			// 在响应头写入新 Token，前端收到后静默替换本地存储的 Token
			w.Header().Set(RefreshTokenHeader, newToken)
			logx.Infof("[zero-gateway] Token 已续期, userId: %s", claims.BaseClaims.ID)
		}

		next(w, r)
	}
}

// tryRefresh 判断是否需要续期，需要则签发新 Token 并返回
// 续期规则：剩余有效时间 < BufferTime → 以原始有效时长（ExpiresAt - NotBefore）重新签发
func (m *AuthMiddleware) tryRefresh(j *jwtUtil.JWT, claims *jwtUtil.CustomClaims) (string, bool) {
	bufferTime := time.Duration(claims.BufferTime) * time.Second
	expiresAt := claims.RegisteredClaims.ExpiresAt.Time
	remaining := time.Until(expiresAt)

	// 未进入缓冲窗口，无需续期
	if remaining >= bufferTime {
		return "", false
	}

	// 计算原始有效时长：ExpiresAt - NotBefore ≈ 当初登录时配置的 activeTimeout
	notBefore := claims.RegisteredClaims.NotBefore.Time
	originalDuration := expiresAt.Sub(notBefore)

	// 构建新 Claims，保持 BaseClaims 和 BufferTime 不变，重新计算有效期
	newClaims := jwtUtil.CustomClaims{
		BaseClaims: claims.BaseClaims,
		BufferTime: claims.BufferTime,
		RegisteredClaims: jwtv5.RegisteredClaims{
			Audience:  claims.RegisteredClaims.Audience,
			Issuer:    claims.RegisteredClaims.Issuer,
			NotBefore: jwtv5.NewNumericDate(time.Now()),
			ExpiresAt: jwtv5.NewNumericDate(time.Now().Add(originalDuration)),
		},
	}

	newToken, err := j.CreateToken(newClaims)
	if err != nil {
		logx.Errorf("[zero-gateway] Token 续期失败: %v", err)
		return "", false
	}
	return newToken, true
}

// isWhitelisted 支持精确匹配和 /* 前缀通配
func (m *AuthMiddleware) isWhitelisted(path string) bool {
	if m.whitelist[path] {
		return true
	}
	for p := range m.whitelist {
		if strings.HasSuffix(p, "/*") && strings.HasPrefix(path, strings.TrimSuffix(p, "*")) {
			return true
		}
	}
	return false
}

// writeUnauthorized 返回统一的 401 响应
func writeUnauthorized(w http.ResponseWriter, msg string) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(http.StatusUnauthorized)
	_ = json.NewEncoder(w).Encode(map[string]interface{}{
		"code": 401,
		"msg":  msg,
	})
}
