// Package middleware 提供 Guardrail 与限流等接入层中间件。
package middleware

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/sundynix/sundynix-gateway/internal/guardrail"
	"github.com/sundynix/sundynix-gateway/internal/store"
)

// Guardrail 实现 Harness 输入护栏：拦截提示词注入 / 超大请求体。
// 只检查带 JSON 体的写请求（POST/PUT）；文件上传(multipart)与 GET/SSE 不经此。
// 输出护栏不在此做 —— Token 流为 SSE 实时流，网关缓冲会破坏流式，输出过滤应在
// dispatcher 的 token 发射层（见 PROGRESS 路线图）。
// 命中（blocked/suspect）除打日志外，best-effort 落库 guardrail_event 供安全溯源。
func Guardrail(db *store.Postgres) gin.HandlerFunc {
	return func(c *gin.Context) {
		if m := c.Request.Method; (m == http.MethodPost || m == http.MethodPut) &&
			strings.HasPrefix(c.GetHeader("Content-Type"), "application/json") {
			// 限读上限 + 1 字节以判定"过大"；命中拦截则后续 handler 不执行。
			body, _ := io.ReadAll(io.LimitReader(c.Request.Body, guardrail.MaxJSONBytes+1))
			res := guardrail.Inspect(body)
			if res.Blocked {
				log.Printf("[guardrail] 拦截 %s %s：%s", c.Request.Method, c.Request.URL.Path, res.Reason)
				recordGuardrail(c, db, "blocked", res.Reason, res.Signals)
				c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{"error": "输入护栏拦截：" + res.Reason})
				return
			}
			if res.Suspect { // 灰区：放行但打标，交 Dispatcher 的 LLM 分类器(Tier2)裁决
				log.Printf("[guardrail] 灰区放行 %s %s：软信号 %v", c.Request.Method, c.Request.URL.Path, res.Signals)
				c.Set("guardrail_suspect", true)
				recordGuardrail(c, db, "suspect", res.Reason, res.Signals)
			}
			c.Request.Body = io.NopCloser(bytes.NewReader(body)) // 还原请求体供后续 handler 读取
		}
		c.Next()
	}
}

// recordGuardrail best-effort 落库一条护栏事件（独立超时 ctx，失败静默）。
func recordGuardrail(c *gin.Context, db *store.Postgres, kind, reason string, signals []string) {
	if db == nil {
		return
	}
	uid, _ := c.Get(CtxUserID)
	actor, _ := uid.(string)
	sig := ""
	if len(signals) > 0 {
		if b, err := json.Marshal(signals); err == nil {
			sig = string(b)
		}
	}
	e := &store.GuardrailEvent{
		Actor: actor, Kind: kind, Reason: reason, Signals: sig,
		Method: c.Request.Method, Path: c.Request.URL.Path, IP: c.ClientIP(),
	}
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	_ = db.AppendGuardrailEvent(ctx, e)
}

// RateLimit 基于 Redis 的会话级限流（每分钟上限）。
// 限流键：**已认证用户优先按 uid，未认证按客户端 IP** —— 企业网多人共享出口 IP 不再互相拖累，
// 单用户换 IP 也绕不过。须挂在 Auth 之后（否则取不到 uid）。上限经 RATE_LIMIT_PER_MIN 配置
// （缺省 120）；压测可调高。Redis 降级时始终放行，不阻断业务。
func RateLimit(cache *store.Redis) gin.HandlerFunc {
	perMinute := int64(envInt("RATE_LIMIT_PER_MIN", 120))
	return func(c *gin.Context) {
		key := "ip:" + c.ClientIP()
		if v, ok := c.Get(CtxUserID); ok {
			if uid, _ := v.(string); uid != "" {
				key = "u:" + uid
			}
		}
		ok, _ := cache.Allow(c.Request.Context(), key, perMinute, time.Minute)
		if !ok {
			c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"})
			return
		}
		c.Next()
	}
}
