// Package router 装配 Gin 统一接入层的路由与中间件。
package router

import (
	"log"
	"os"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"

	"github.com/sundynix/sundynix-gateway/internal/blob"
	"github.com/sundynix/sundynix-gateway/internal/handler"
	"github.com/sundynix/sundynix-gateway/internal/middleware"
	"github.com/sundynix/sundynix-gateway/internal/nats"
	"github.com/sundynix/sundynix-gateway/internal/store"
)

// New 构建带有 Guardrail / 限流中间件的 Gin 引擎。
func New(db *store.Postgres, cache *store.Redis, bus *nats.Bus, blobStore *blob.Store) *gin.Engine {
	r := gin.New()
	r.Use(gin.Recovery())                         // panic 兜底
	r.Use(otelgin.Middleware("sundynix-gateway")) // OTel: HTTP server span（链路根 + 提取上游 traceparent）
	r.Use(middleware.RequestID())                 // 生成/透传 X-Request-ID（日志关联）
	r.Use(middleware.Observe())                   // Prometheus 指标 + 结构化访问日志（替代 gin 默认文本日志）
	r.Use(cors())                   // 桌面端/浏览器跨源访问
	r.Use(middleware.Auth())            // 解析 Bearer JWT，注入已验证 userID（非阻断）——须在限流前，供按用户限流
	r.Use(middleware.TenantContext(db)) // 多租户：注入当前 tenant_id（已登录才解析；须在 Auth 之后）
	r.Use(middleware.RateLimit(cache))  // 已认证按用户限流，否则按 IP（企业网多人共享 IP 不再互相拖累）
	r.Use(middleware.Guardrail(db))     // Harness: Input Guardrail（命中落库 guardrail_event）

	h := handler.New(db, cache, bus, blobStore)

	// 可观测性根端点：Prometheus 抓取 + k8s 存活/就绪探针（不挂业务中间件鉴权）。
	r.GET("/metrics", gin.WrapH(promhttp.Handler()))
	r.GET("/healthz", h.Healthz)
	r.GET("/readyz", h.Readyz)

	api := r.Group("/api/v1")
	{
		// —— 公开：鉴权端点 / 健康 / 按 task_id 寻址的 SSE 与导出（EventSource/下载无法带 Bearer）——
		api.POST("/auth/register", h.Register)             // 注册 + 签发 JWT
		api.POST("/auth/login", h.Login)                   // 登录 + 签发 JWT
		api.GET("/auth/me", h.Me)                          // 当前登录用户（无效令牌 → 401）
		api.GET("/health", h.Health)                       // 依赖健康聚合（顶栏五盏灯）
		api.GET("/tasks/:id/stream", h.StreamTask)         // SSE 回流 Token Stream（task_id 寻址）
		api.GET("/tasks/:id/exec", h.StreamExec)           // SSE 回流执行轨迹（task_id 寻址）
		api.GET("/kb/ingest/:id/stream", h.KbIngestStream) // 入库进度 SSE（job_id 寻址）
		api.GET("/reports/:id/export", h.ExportReport)     // 按需导出（report_id 寻址）
		api.GET("/reports/:id/download", h.ExportReport)   // 兼容旧入口（默认 docx）

		// —— 受保护：owner 作用域业务，必须携带有效 JWT ——
		p := api.Group("", middleware.RequireAuth())
		{
			p.POST("/tasks", h.SubmitTask)              // 解析 DSL 并 Publish 到 NATS（带已验证 uid）
			p.GET("/tasks/:id", h.TaskStatus)           // 任务生命周期状态（UI 轮询 submitted/running/done/failed/timeout/waiting/rejected）
			p.POST("/tasks/:id/approve", middleware.Audit(db), h.ApproveTask) // HITL 人工审批决定（批准/拒绝，审计）
			p.GET("/tenants/current", h.TenantCurrent)  // 当前租户上下文 + 角色（多租户）
			p.GET("/tasks/:id/eval", h.TaskEval)        // 自动化评测结果（综合/质量/忠实度/分级）
			p.PUT("/memory", h.SetMemory)               // 偏好记忆登记（→ mcp-go memory_upsert）
			p.GET("/memory", h.ListMemory)              // 列出当前用户偏好（记忆面板）
			p.DELETE("/memory", h.DeleteMemory)         // 软删一条偏好（?key=）
			p.GET("/kb/list", h.KbList)                 // 当前用户的知识库列表（owner 隔离）
			p.POST("/kb/create", h.KbCreate)            // 新建知识库
			p.POST("/kb/ingest", h.KbIngest)            // 文本入库
			p.POST("/kb/ingest_file", h.KbIngestFile)   // 文件入库
			p.POST("/kb/search", h.KbSearch)            // 检索台
			p.GET("/kb/vault", h.KbVault)               // 文库列表
			p.GET("/kb/doc", h.KbDoc)                   // 取单篇文档
			p.DELETE("/kb/doc", h.KbDeleteDoc)          // 级联删文档（三库+MinIO+PG）

			// Prompt 控制面（平台级配置：建版本 → 激活 → 控制面热下发各服务）
			p.GET("/prompts", h.PromptList)                 // 列出全部版本 + 可配键
			p.POST("/prompts/version", h.PromptCreateVersion) // 建新版本（不自动激活）
			p.POST("/prompts/activate", middleware.Audit(db), h.PromptActivate)     // 激活某版本 → 广播热更新（审计）
			p.POST("/prompts/deactivate", middleware.Audit(db), h.PromptDeactivate) // 撤销激活 → 回退代码默认（热，审计）
			p.GET("/kb/links", h.KbLinks)               // 某库双链
			p.POST("/kb/note", h.KbSaveNote)            // 新建/编辑笔记
			p.GET("/kb/graph", h.KbGraph)               // 知识图谱三元组
			p.GET("/agents", h.AgentList)               // 我的编排列表（owner 隔离）
			p.POST("/agents", h.AgentSave)              // 保存/更新编排
			p.DELETE("/agents", h.AgentDelete)          // 删除编排
			p.POST("/reports", h.GenerateReport)        // 报告生成
			p.GET("/billing", h.Billing)
			p.GET("/stats/overview", h.StatsOverview) // 工作台仪表盘聚合
			p.GET("/runs", h.Runs)                    // 运行历史（复盘）
			p.GET("/tasks/:id/replay", h.TaskReplay)  // 历史运行复盘（持久化输出+轨迹，免 Redis TTL）
		}

		// 运维控制面：LLM 模型配置（含 API 密钥管理）—— 必须管理员（RequireAdmin）。
		admin := api.Group("/admin", middleware.RequireAdmin(), middleware.Audit(db))
		{
			admin.GET("/models", h.ListModels)
			admin.POST("/models", h.SaveModel)
			admin.POST("/models/:id/active", h.SetActiveModel)
			admin.DELETE("/models/:id", h.DeleteModel)
			admin.POST("/models/test", h.TestModel)
			admin.GET("/pricing", h.ListPricing) // 各模型计价（token↔真钱）
			admin.PUT("/pricing", h.SavePricing) // 设置某模型输入/输出单价
			admin.GET("/status", h.AdminStatus)      // 服务状态：基建/服务探活 + MCP 工具注册
			admin.GET("/overview", h.AdminOverview)  // 系统级聚合：全平台用户/任务/评测/模型态/提示词态/健康
			admin.GET("/usage", h.AdminUsage)        // 用量/积分/成本：全平台按天趋势 + 租户排行 / 单租户余额
			admin.GET("/audit", h.AuditList)                  // 敏感操作审计流（倒序，翻页）
			admin.GET("/guardrail-events", h.GuardrailEvents) // 护栏命中安全事件流（倒序，翻页）
		}
	}
	return r
}

// cors 控制跨源访问。允许来源经 CORS_ALLOW_ORIGIN 配置。
// 安全默认：开发期缺省放行 "*"（便利）；生产期(APP_ENV=prod / GIN_MODE=release)未显式配置则
// **不放行任意源**（不发 ACAO 头 → 浏览器按同源策略拦截），逼运维显式配置允许的源。
func cors() gin.HandlerFunc {
	origin := os.Getenv("CORS_ALLOW_ORIGIN")
	if origin == "" {
		if isProd() {
			log.Printf("[cors] 生产环境未设 CORS_ALLOW_ORIGIN：默认不放行跨源，请显式配置允许的源（如 https://app.example.com）")
		} else {
			origin = "*" // 开发便利
		}
	}
	return func(c *gin.Context) {
		if origin != "" {
			c.Header("Access-Control-Allow-Origin", origin)
			if origin != "*" {
				c.Header("Vary", "Origin")
			}
		}
		c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Session-ID, X-User-ID")
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}
		c.Next()
	}
}

// isProd 判定生产环境（与 middleware.RequireAdmin 同口径）。
func isProd() bool {
	return strings.EqualFold(os.Getenv("APP_ENV"), "production") ||
		strings.EqualFold(os.Getenv("APP_ENV"), "prod") ||
		strings.EqualFold(os.Getenv("GIN_MODE"), "release")
}
