package api

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

	"github.com/gin-gonic/gin"

	"oci-portal/internal/model"
	"oci-portal/internal/service"
)

// usernameKey 是鉴权通过后写入 gin.Context 的用户名键。
const usernameKey = "username"

// RequireAuth 校验 Authorization: Bearer 令牌（签名、有效期与令牌版本），
// 通过后把用户名放进上下文。
func RequireAuth(auth *service.AuthService) gin.HandlerFunc {
	return func(c *gin.Context) {
		token, ok := strings.CutPrefix(c.GetHeader("Authorization"), "Bearer ")
		if !ok || token == "" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing bearer token"})
			return
		}
		username, err := auth.ParseToken(c.Request.Context(), token)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid or expired token"})
			return
		}
		c.Set(usernameKey, username)
		c.Next()
	}
}

// bodyLimit 给请求体套上限(S-03):超限读取由 MaxBytesReader 截断报错,
// 绑定失败返回 400;webhook 入口另有独立 256KiB 自限,不经此中间件。
func bodyLimit(n int64) gin.HandlerFunc {
	return func(c *gin.Context) {
		if c.Request.Body != nil {
			c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, n)
		}
		c.Next()
	}
}

// systemLogMiddleware 把写请求（POST/PUT/DELETE）异步记入系统日志，只读请求跳过。
// 只记方法、路径等元数据，绝不读请求体——请求体可能含私钥 / 口令等敏感数据。
func systemLogMiddleware(logs *service.SystemLogService) gin.HandlerFunc {
	return func(c *gin.Context) {
		if !isWriteMethod(c.Request.Method) {
			c.Next()
			return
		}
		start := time.Now()
		tee := &teeWriter{ResponseWriter: c.Writer}
		c.Writer = tee
		c.Next()
		logs.Record(model.SystemLog{
			Username:   c.GetString(usernameKey),
			Method:     c.Request.Method,
			Path:       requestPath(c),
			Status:     c.Writer.Status(),
			DurationMs: time.Since(start).Milliseconds(),
			ClientIP:   requestIP(c),
			UserAgent:  truncateLogField(c.Request.UserAgent(), 256),
			ErrMsg:     errMsgOf(c.Writer.Status(), tee.head),
		})
	}
}

// teeWriter 旁路捕获响应体前 512 字节,供失败留痕提取 error 文案;不改写响应。
type teeWriter struct {
	gin.ResponseWriter
	head []byte
}

func (w *teeWriter) Write(b []byte) (int, error) {
	if room := 512 - len(w.head); room > 0 {
		if len(b) < room {
			room = len(b)
		}
		w.head = append(w.head, b[:room]...)
	}
	return w.ResponseWriter.Write(b)
}

// errMsgOf 从失败响应(>=400)的 JSON 中提取 error 字段;错误响应均由
// respondError 系列生成、不含敏感信息,成功响应一律不记。
func errMsgOf(status int, head []byte) string {
	if status < 400 || len(head) == 0 {
		return ""
	}
	var body struct {
		Error string `json:"error"`
	}
	if json.Unmarshal(head, &body) != nil || body.Error == "" {
		return ""
	}
	return truncateLogField(body.Error, 256)
}

// truncateLogField 按字节截断留痕字段,防超长 UA / 错误串撑爆列宽。
func truncateLogField(s string, max int) string {
	if len(s) <= max {
		return s
	}
	return s[:max]
}

// isWriteMethod 判断是否需要留痕的写方法。
func isWriteMethod(m string) bool {
	return m == http.MethodPost || m == http.MethodPut || m == http.MethodDelete
}

// requestPath 优先取注册的路由模板（含 :id 占位符，避免把敏感 query 记入日志），
// 未匹配到路由时退回原始路径；webhook 前缀的原始路径把 secret 段脱敏。
func requestPath(c *gin.Context) string {
	if p := c.FullPath(); p != "" {
		return p
	}
	return sanitizeLogPath(c.Request.URL.Path)
}

// sanitizeLogPath 把回传 webhook 路径的最后一段（secret）替换为 ***；
// 正常留痕走路由模板不经此处，这里是未匹配路由时的防御兜底。
func sanitizeLogPath(path string) string {
	const prefix = "/api/v1/webhooks/"
	if !strings.HasPrefix(path, prefix) {
		return path
	}
	if i := strings.LastIndex(path, "/"); i >= len(prefix) {
		return path[:i+1] + "***"
	}
	return path
}
