package server

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

	"kra/internal/conf"
	websocket "kra/internal/integration/websocket"
	"kra/internal/server/handler"
	"kra/internal/server/middleware"
	"kra/internal/server/router"
	"kra/internal/server/staticfiles"
	"kra/internal/service"
	"kra/pkg/httpx"
	platformmodule "kra/pkg/module"

	"github.com/gin-gonic/gin"
	kratoshttp "github.com/go-kratos/kratos/v3/transport/http"
)

func NewGinEngine(runtime *conf.Runtime, access *service.AccessControlService, handlers *handler.Set, auth *service.AuthService, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string) *gin.Engine {
	return NewGinEngineWithRuntime(runtime, access, auth, security, audit, logger, version, platformmodule.NewRuntime(router.NewRoutes(handlers)), nil)
}

func NewGinEngineWithRuntime(runtime *conf.Runtime, access *service.AccessControlService, auth middleware.TokenAuthenticator, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string, routes *platformmodule.Runtime, ws *websocket.Server) *gin.Engine {
	if runtime == nil {
		runtime = conf.NewRuntime(nil, nil)
	}
	gin.SetMode(gin.ReleaseMode)
	engine := gin.New()
	if err := engine.SetTrustedProxies(nil); err != nil && logger != nil {
		logger.Error("disable trusted proxies failed", "error", err)
	}
	engine.Use(middleware.RequestMeta(), middleware.Recovery(logger), middleware.AccessLog(runtime, logger, version), middleware.CORS(runtime), middleware.ErrorAudit(logger), middleware.SecurityRateLimit(security))

	prefix := ""
	config := runtime.Admin()
	if config != nil {
		prefix = strings.TrimSuffix(config.RouterPrefix, "/")
	}
	public := engine.Group(prefix)
	public.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, "ok") })

	private := engine.Group(prefix)
	// The reference administration behavior installs operation recording after JWT,
	// password and Casbin/data-scope middleware. Keeping the recorder after the
	// equivalent Kra guards avoids persisting rejected unauthenticated or
	// unauthorized requests as successful business operations.
	private.Use(middleware.Auth(auth), middleware.MustChangePassword(), middleware.AccessControl(runtime, access), middleware.OperationAudit(runtime, audit))
	if routes != nil {
		routes.RegisterRoutes(public, private, engine)
	}
	handleWebSocket := func(c *gin.Context) {
		if ws == nil || !ws.Enabled() || c.Request.URL.Path != ws.Path() {
			c.Status(http.StatusNotFound)
			return
		}
		if !middleware.AuthenticateWebSocket(c, auth) {
			return
		}
		if err := ws.HandleRequest(c.Writer, c.Request); err != nil && logger != nil {
			logger.Warn("websocket request failed", "mod", "websocket", "error", err)
		}
	}
	if ws != nil && ws.Enabled() {
		engine.GET(ws.Path(), handleWebSocket)
	}
	registerSwagger(engine, prefix, version, logger)
	staticfiles.Register(engine, runtime)
	if logger != nil {
		for _, route := range engine.Routes() {
			logger.Info("router registered", "method", route.Method, "path", route.Path)
		}
		logger.Info("router register success", "route_count", len(engine.Routes()))
	}

	engine.NoRoute(func(c *gin.Context) {
		if ws != nil && ws.Enabled() && c.Request.Method == http.MethodGet && c.Request.URL.Path == ws.Path() {
			handleWebSocket(c)
			return
		}
		if staticfiles.Serve(c, runtime) {
			return
		}
		httpx.Fail(c, "请求的接口不存在")
	})
	return engine
}

func NewGinServer(c *conf.Server, engine *gin.Engine) *kratoshttp.Server {
	network, address := "tcp", ":8000"
	if c != nil && c.Http != nil {
		if c.Http.Network != "" {
			network = c.Http.Network
		}
		if c.Http.Addr != "" {
			address = c.Http.Addr
		}
	}
	timeout := 10 * time.Minute
	if c != nil && c.Http != nil && c.Http.Timeout != nil && c.Http.Timeout.AsDuration() > 0 {
		timeout = c.Http.Timeout.AsDuration()
	}
	server := kratoshttp.NewServer(kratoshttp.Network(network), kratoshttp.Address(address), kratoshttp.Timeout(timeout))
	server.HandlePrefix("/", engine)
	server.Server.ReadHeaderTimeout = 10 * time.Second
	server.Server.ReadTimeout = timeout
	server.Server.WriteTimeout = timeout
	server.Server.IdleTimeout = 2 * time.Minute
	server.Server.MaxHeaderBytes = 1 << 20
	return server
}
