package main

import (
	"context"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/edu-cloud/api-gateway/internal/config"
	"github.com/edu-cloud/api-gateway/internal/health"
	"github.com/edu-cloud/api-gateway/internal/middleware"
	"github.com/edu-cloud/api-gateway/internal/observability"
	"github.com/edu-cloud/api-gateway/internal/proxy"
	"github.com/edu-cloud/shared-go/jwks"
	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
)

// maxBodyBytes 请求体大小上限：10MB
const maxBodyBytes int64 = 10 * 1024 * 1024

// version 编译时通过 -ldflags "-X main.version=..." 注入，默认 dev
var version = "dev"

func main() {
	cfg := config.Load()

	// 初始化 OpenTelemetry tracer（W6 裁决：资源属性补全）
	hostName, _ := os.Hostname()
	tracerShutdown := observability.InitTracer("api-gateway", cfg.OTLPEndpoint, cfg.Env, version, hostName)
	defer tracerShutdown()

	// 初始化 JWKS Fetcher（RS256 公钥缓存，P2.2 裁决）
	// DevMode 下也创建 fetcher，使 DevMode 可验证真实 JWT（非 dev-token 场景）
	fetcher := jwks.NewFetcher(cfg.JWKSURL)

	gin.SetMode(gin.ReleaseMode)
	r := gin.New()
	// 关闭尾斜杠重定向：避免 Next.js rewrites 代理时 /api/v1/classes → 301 → /api/v1/classes/ 循环
	r.RedirectTrailingSlash = false

	// 全局中间件（按顺序注册）
	// 1. panic 恢复（最外层，捕获后续所有中间件与 handler 的 panic）
	r.Use(middleware.Recovery())
	// 2. OpenTelemetry 自动埋点（HTTP 请求/响应 span）
	r.Use(otelgin.Middleware("api-gateway"))
	// 3. 请求 ID 注入
	r.Use(middleware.RequestID())
	// 4. 跨域
	r.Use(middleware.CORS(cfg))
	// 5. 安全响应头
	r.Use(middleware.SecurityHeaders())
	// 6. 请求体大小限制
	r.Use(middleware.RequestBodyLimit(maxBodyBytes))
	// 7. 限流（每 IP 100 rps，突发 20）
	r.Use(middleware.RateLimit(100, 20))

	// 健康检查路由（无需鉴权，在 Auth 之前）
	r.GET("/healthz", health.Healthz)
	r.GET("/readyz", health.Readyz(cfg))
	// Prometheus 指标端点
	r.GET("/metrics", gin.WrapH(promhttp.Handler()))

	// API v1 组：熔断 + 鉴权 + 指标 + 反向代理
	api := r.Group("/api/v1")
	// 8. 熔断（仅作用于代理路由，下游 5xx 触发）
	api.Use(middleware.CircuitBreaker("downstream"))
	// 9. JWT 鉴权（RS256，通过 JWKS 公钥校验）
	api.Use(middleware.AuthMiddleware(cfg, fetcher))
	// 10. HTTP 请求指标（统计通过鉴权的请求）
	api.Use(observability.Metrics())
	{
		// classes → core-edu（C1 裁决：classes 合并入 core-edu）
		registerProxy(api, "classes", cfg.CoreEduServiceURL)
		// iam 服务路由（身份与访问管理）
		registerProxy(api, "iam", cfg.IamServiceURL)
		// teacher-bff 路由（教师聚合层 GraphQL）
		registerProxy(api, "teacher", cfg.TeacherBffURL)
		// student-bff 路由（学生聚合层 GraphQL，P3）
		registerProxy(api, "student", cfg.StudentBffURL)
		// parent-bff 路由（家长聚合层 GraphQL，P4）
		registerProxy(api, "parent", cfg.ParentBffURL)
		// core-edu 域路由（考试/作业/成绩）
		registerProxy(api, "exams", cfg.CoreEduServiceURL)
		registerProxy(api, "homework", cfg.CoreEduServiceURL)
		registerProxy(api, "grades", cfg.CoreEduServiceURL)
		// content 域路由（教材/章节/知识点/题库）
		registerProxy(api, "textbooks", cfg.ContentServiceURL)
		registerProxy(api, "chapters", cfg.ContentServiceURL)
		registerProxy(api, "knowledge-points", cfg.ContentServiceURL)
		registerProxy(api, "questions", cfg.ContentServiceURL)
		// msg 域路由（通知/消息）
		registerProxy(api, "notifications", cfg.MsgServiceURL)
		registerProxy(api, "messages", cfg.MsgServiceURL)
		// ai 服务路由（AI 聊天/生成/优化）
		registerProxy(api, "ai", cfg.AiServiceURL)
		// data-ana 域路由（学情诊断/错题本/仪表盘）
		registerProxy(api, "analytics", cfg.DataAnaServiceURL)
		registerProxy(api, "dashboard", cfg.DataAnaServiceURL)
	}

	srv := &http.Server{
		Addr:         ":" + cfg.Port,
		Handler:      r,
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 30 * time.Second,
	}

	// 优雅关闭
	go func() {
		slog.Info("API Gateway listening",
			"port", cfg.Port,
			"env", cfg.Env,
			"dev_mode", cfg.DevMode,
			"version", version,
		)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			slog.Error("listen failed", "error", err)
			os.Exit(1)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	slog.Info("shutting down API Gateway")

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		slog.Error("server forced to shutdown", "error", err)
	}
	slog.Info("API Gateway exited")
}

// registerProxy 创建反向代理并注册无尾斜杠与通配符两条路由。
// RedirectTrailingSlash=false 时 Gin 不会自动跳转，故两条路由都要显式注册。
func registerProxy(api *gin.RouterGroup, prefix, targetURL string) {
	p, err := proxy.NewProxy(targetURL)
	if err != nil {
		slog.Error("failed to create proxy", "prefix", prefix, "target", targetURL, "error", err)
		panic(err)
	}
	handler := proxy.ProxyHandler(p)
	api.Any("/"+prefix, handler)
	api.Any("/"+prefix+"/*path", handler)
}
