package api

import (
	"net/http"
	"reflect"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/codec/json"
	"github.com/gorilla/websocket"
	"github.com/rs/zerolog/log"
	apiV1 "github.com/yusing/godoxy/internal/api/v1"
	agentApi "github.com/yusing/godoxy/internal/api/v1/agent"
	authApi "github.com/yusing/godoxy/internal/api/v1/auth"
	certApi "github.com/yusing/godoxy/internal/api/v1/cert"
	dockerApi "github.com/yusing/godoxy/internal/api/v1/docker"
	fileApi "github.com/yusing/godoxy/internal/api/v1/file"
	homepageApi "github.com/yusing/godoxy/internal/api/v1/homepage"
	metricsApi "github.com/yusing/godoxy/internal/api/v1/metrics"
	routeApi "github.com/yusing/godoxy/internal/api/v1/route"
	"github.com/yusing/godoxy/internal/auth"
	"github.com/yusing/godoxy/internal/common"
	apitypes "github.com/yusing/goutils/apitypes"
	gperr "github.com/yusing/goutils/errs"
)

// @title           GoDoxy API
// @version         1.0
// @description     GoDoxy API
// @termsOfService  https://github.com/yusing/godoxy/blob/main/LICENSE

// @contact.name   Yusing
// @contact.url    https://github.com/yusing/godoxy/issues

// @license.name  MIT
// @license.url   https://github.com/yusing/godoxy/blob/main/LICENSE

// @BasePath  /api/v1

// @externalDocs.description  GoDoxy Docs
// @externalDocs.url          https://docs.godoxy.dev
func NewHandler() *gin.Engine {
	if !common.IsDebug {
		gin.SetMode("release")
	}
	r := gin.New()
	r.Use(ErrorHandler())
	r.Use(ErrorLoggingMiddleware())
	r.Use(NoCache())

	log.Debug().Msg("gin codec json.API: " + reflect.TypeOf(json.API).Name())

	r.GET("/api/v1/version", apiV1.Version)

	if auth.IsEnabled() {
		v1Auth := r.Group("/api/v1/auth")
		{
			v1Auth.HEAD("/check", authApi.Check)
			v1Auth.POST("/login", authApi.Login)
			v1Auth.GET("/callback", authApi.Callback)
			v1Auth.POST("/callback", authApi.Callback)
			v1Auth.POST("/logout", authApi.Logout)
			v1Auth.GET("/logout", authApi.Logout)
		}
	}

	v1 := r.Group("/api/v1")
	if auth.IsEnabled() {
		v1.Use(AuthMiddleware())
	}
	if common.APISkipOriginCheck {
		v1.Use(SkipOriginCheckMiddleware())
	}
	{
		// enable cache for favicon
		v1.GET("/favicon", apiV1.FavIcon)
		v1.GET("/health", apiV1.Health)
		v1.GET("/icons", apiV1.Icons)
		v1.POST("/reload", apiV1.Reload)
		v1.GET("/stats", apiV1.Stats)

		route := v1.Group("/route")
		{
			route.GET("/list", routeApi.Routes)
			route.GET("/:which", routeApi.Route)
			route.GET("/providers", routeApi.Providers)
			route.GET("/by_provider", routeApi.ByProvider)
			route.POST("/playground", routeApi.Playground)
		}

		file := v1.Group("/file")
		{
			file.GET("/list", fileApi.List)
			file.GET("/content", fileApi.Get)
			file.PUT("/content", fileApi.Set)
			file.POST("/content", fileApi.Set)
			file.POST("/validate", fileApi.Validate)
		}

		homepage := v1.Group("/homepage")
		{
			homepage.GET("/categories", homepageApi.Categories)
			homepage.GET("/items", homepageApi.Items)
			homepage.POST("/set/item", homepageApi.SetItem)
			homepage.POST("/set/items_batch", homepageApi.SetItemsBatch)
			homepage.POST("/set/item_visible", homepageApi.SetItemVisible)
			homepage.POST("/set/item_favorite", homepageApi.SetItemFavorite)
			homepage.POST("/set/item_sort_order", homepageApi.SetItemSortOrder)
			homepage.POST("/set/item_all_sort_order", homepageApi.SetItemAllSortOrder)
			homepage.POST("/set/item_fav_sort_order", homepageApi.SetItemFavSortOrder)
			homepage.POST("/set/category_order", homepageApi.SetCategoryOrder)
			homepage.POST("/item_click", homepageApi.ItemClick)
		}

		cert := v1.Group("/cert")
		{
			cert.GET("/info", certApi.Info)
			cert.GET("/renew", certApi.Renew)
		}

		agent := v1.Group("/agent")
		{
			agent.GET("/list", agentApi.List)
			agent.POST("/create", agentApi.Create)
			agent.POST("/verify", agentApi.Verify)
		}

		metrics := v1.Group("/metrics")
		{
			metrics.GET("/system_info", metricsApi.SystemInfo)
			metrics.GET("/all_system_info", metricsApi.AllSystemInfo)
			metrics.GET("/uptime", metricsApi.Uptime)
		}

		docker := v1.Group("/docker")
		{
			docker.GET("/container/:id", dockerApi.GetContainer)
			docker.GET("/containers", dockerApi.Containers)
			docker.GET("/info", dockerApi.Info)
			docker.GET("/logs/:id", dockerApi.Logs)
			docker.POST("/start", dockerApi.Start)
			docker.POST("/stop", dockerApi.Stop)
			docker.POST("/restart", dockerApi.Restart)
		}
	}

	return r
}

func NoCache() gin.HandlerFunc {
	return func(c *gin.Context) {
		// skip cache if Cache-Control header is set
		if c.Writer.Header().Get("Cache-Control") == "" {
			c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
			c.Header("Pragma", "no-cache")
			c.Header("Expires", "0")
		}
		c.Next()
	}
}

func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		err := auth.GetDefaultAuth().CheckToken(c.Request)
		if err != nil {
			c.JSON(http.StatusUnauthorized, apitypes.Error("Unauthorized", err))
			c.Abort()
			return
		}
		c.Next()
	}
}

func SkipOriginCheckMiddleware() gin.HandlerFunc {
	upgrader := &websocket.Upgrader{
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
	return func(c *gin.Context) {
		c.Set("upgrader", upgrader)
		c.Next()
	}
}

func ErrorHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Next()
		if len(c.Errors) > 0 {
			logger := log.With().Str("uri", c.Request.RequestURI).Logger()
			for _, err := range c.Errors {
				gperr.LogError("Internal error", err.Err, &logger)
			}
			if !c.IsWebsocket() {
				c.JSON(http.StatusInternalServerError, apitypes.Error("Internal server error"))
			}
		}
	}
}

func ErrorLoggingMiddleware() gin.HandlerFunc {
	return gin.CustomRecoveryWithWriter(nil, func(c *gin.Context, err any) {
		log.Error().Any("error", err).Str("uri", c.Request.RequestURI).Msg("Internal error")
		if !c.IsWebsocket() {
			c.JSON(http.StatusInternalServerError, apitypes.Error("Internal server error"))
		}
	})
}
