package middleware

import (
	"log/slog"
	"net/http"
	"strings"

	"github.com/edu-cloud/api-gateway/internal/config"
	"github.com/edu-cloud/api-gateway/internal/observability"
	"github.com/edu-cloud/shared-go/jwks"
	"github.com/gin-gonic/gin"
)

// publicPaths 是无需鉴权的公开路径（精确匹配，基于去掉 /api/v1 前缀后的路径）
var publicPaths = map[string]bool{
	"/iam/register": true,
	"/iam/login":    true,
	"/iam/refresh":  true,
}

// isPublicPath 判断请求路径是否属于公开路径（无需鉴权）
// 匹配规则：去掉 /api/v1 前缀后，与 publicPaths 精确匹配
func isPublicPath(path string) bool {
	stripped := strings.TrimPrefix(path, "/api/v1")
	return publicPaths[stripped]
}

// AuthMiddleware 验证 JWT（RS256，通过 IAM JWKS 公钥校验）并注入用户信息到请求头。
//
// P2 升级：HS256 → RS256（shared-go/jwks.Fetcher），JWT 由 IAM 签发、Gateway 公钥校验。
// DevMode 旁路：接受 "dev-token"，注入固定开发身份（仅非生产环境，W7 防护在 config.Load）。
//
// 注入下游头：
//   - x-user-id（来自 claims.UserID）
//   - x-user-roles（来自 claims.Role）
//   - x-data-scope（来自 claims.DataScope）
//
// fetcher 为 nil 时（DevMode）跳过真实验签，仅走 dev-token 旁路。
func AuthMiddleware(cfg *config.Config, fetcher *jwks.Fetcher) gin.HandlerFunc {
	return func(c *gin.Context) {
		// 公开路径白名单（register/login/refresh 无需鉴权）
		if isPublicPath(c.Request.URL.Path) {
			c.Next()
			return
		}

		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			observability.IncAuthFailure("missing_auth_header")
			abortGW(c, http.StatusUnauthorized, "GW_UNAUTHORIZED", "missing authorization header")
			return
		}

		tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
		if tokenStr == authHeader {
			observability.IncAuthFailure("invalid_scheme")
			abortGW(c, http.StatusUnauthorized, "GW_UNAUTHORIZED", "invalid authorization scheme, expected Bearer")
			return
		}

		// 开发模式旁路：DEV_MODE=true 时接受 "dev-token"，注入开发用户
		// 仅用于本地联调，生产环境必须关闭 DEV_MODE（W7 防护在 config.Load 拦截）
		if cfg.DevMode && tokenStr == "dev-token" {
			c.Request.Header.Set("x-user-id", "dev-user")
			c.Request.Header.Set("x-user-roles", "teacher,admin")
			c.Request.Header.Set("x-data-scope", "SCHOOL")
			c.Next()
			return
		}

		// 非 DevMode 必须有 fetcher（RS256 验签）
		if fetcher == nil {
			observability.IncAuthFailure("fetcher_nil")
			slog.Error("jwks fetcher is nil in non-dev mode")
			abortGW(c, http.StatusInternalServerError, "GW_INTERNAL_ERROR", "auth not initialized")
			return
		}

		// RS256 验签（shared-go/jwks.Fetcher 内部按 kid 取公钥 + 缓存）
		claims, err := fetcher.ValidateToken(tokenStr)
		if err != nil {
			observability.IncAuthFailure("invalid_token")
			slog.Warn("jwt validation failed", "error", err)
			abortGW(c, http.StatusUnauthorized, "GW_INVALID_TOKEN", "token validation failed")
			return
		}

		// iss / aud 校验（Config 定义但之前未生效）
		if cfg.JWTIssuer != "" && claims.Issuer != cfg.JWTIssuer {
			observability.IncAuthFailure("invalid_issuer")
			abortGW(c, http.StatusUnauthorized, "GW_INVALID_CLAIMS", "invalid issuer")
			return
		}
		if cfg.JWTAudience != "" {
			audMatch := false
			for _, a := range claims.Audience {
				if a == cfg.JWTAudience {
					audMatch = true
					break
				}
			}
			if !audMatch {
				observability.IncAuthFailure("invalid_audience")
				abortGW(c, http.StatusUnauthorized, "GW_INVALID_CLAIMS", "invalid audience")
				return
			}
		}

		// 注入用户信息到下游请求头
		if claims.UserID != "" {
			c.Request.Header.Set("x-user-id", claims.UserID)
		}
		if claims.Role != "" {
			c.Request.Header.Set("x-user-roles", claims.Role)
		}
		if claims.DataScope != "" {
			c.Request.Header.Set("x-data-scope", claims.DataScope)
		}

		c.Next()
	}
}

// abortGW 统一返回 ActionState 信封格式的错误响应（W1/W2 裁决）。
// 响应体：{"success":false,"error":{"code":"<code>","message":"<msg>"}}
func abortGW(c *gin.Context, status int, code, message string) {
	c.AbortWithStatusJSON(status, gin.H{
		"success": false,
		"error": gin.H{
			"code":    code,
			"message": message,
		},
	})
}
