package api

import (
	"easyaudioencode/internal/web/api/static"
	"expvar"
	statics "github.com/gin-contrib/static"
	"log/slog"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"runtime/debug"
	"sort"
	"strings"
	"time"

	"easyaudioencode/domain/version/versionapi"

	localweb "easyaudioencode/pkg/web"

	"git.lnton.com/lnton/pkg/web"
	"github.com/gin-gonic/gin"
)

var startRuntime = time.Now()

func setupRouter(r *gin.Engine, uc *Usecase) {
	r.Use(
		// 格式化输出到控制台，然后记录到日志
		// 此处不做 recover，底层 http.server 也会 recover，但不会输出方便查看的格式
		gin.CustomRecovery(func(c *gin.Context, err any) {
			slog.Error("panic", "err", err, "stack", string(debug.Stack()))
			c.AbortWithStatus(http.StatusInternalServerError)
		}),
		web.Metrics(),
		web.Logger(),
		// debug 环境中配合 debug 日志级别，记录请求体与响应体
		web.LoggerWithBody(web.DefaultBodyLimit, func(_ *gin.Context) bool {
			// true: 表示忽略记录日志
			// !debug 表示非调试环境不记录
			return !uc.Conf.Debug
		}),
	)
	go web.CountGoroutines(10*time.Minute, 20)

	auth := localweb.AuthMiddleware(uc.Conf.Server.HTTP.JwtSecret, uc.Conf.Plugin.HttpAPI+"/extensions/auth", "")
	r.Any("/health", web.WrapH(uc.getHealth))
	r.GET("/app/metrics/api", web.WrapH(uc.getMetricsAPI))
	//快照
	dir, _ := os.Getwd()
	uploadsDir := filepath.Join(dir, "uploads")
	r.Use(statics.Serve("/uploads", statics.LocalFile(uploadsDir, true)))

	versionapi.Register(r, uc.Version, auth)
	RegisterHostAPI(r, uc)
	RegisterAudioEncode(r, uc.AudioEncodeAPI)

	r.NoRoute(func(ctx *gin.Context) {
		p := ctx.Request.URL.Path
		if strings.HasPrefix(p, "/web/") {
			q := ctx.Request.URL.RawQuery
			target := "/web/"
			if q != "" {
				target = target + "?" + q
			}
			ctx.Redirect(http.StatusTemporaryRedirect, target)
			return
		}
		if strings.HasPrefix(p, "/uploads/") {
			q := ctx.Request.URL.RawQuery
			target := "/uploads/"
			if q != "" {
				target = target + "?" + q
			}
			ctx.Redirect(http.StatusTemporaryRedirect, target)
			return
		}

		if strings.HasPrefix(p, "/extensions/easyaudioencode") {
			// 改为前缀替换并在当前请求内重新分发，而不是重定向
			newPath := strings.TrimPrefix(p, "/extensions/easyaudioencode")
			ctx.Request.URL.Path = newPath
			r.HandleContext(ctx)
			return
		}
		ctx.AbortWithStatus(http.StatusNotFound)
	})

	// 直接返回静态文件系统中的入口页
	r.StaticFS("/web/", static.FileSystem())

}

type getHealthOutput struct {
	Version   string    `json:"version"`
	StartAt   time.Time `json:"start_at"`
	GitBranch string    `json:"git_branch"`
	GitHash   string    `json:"git_hash"`
}

func (uc *Usecase) getHealth(_ *gin.Context, _ *struct{}) (getHealthOutput, error) {
	return getHealthOutput{
		Version:   uc.Conf.BuildVersion,
		GitBranch: strings.Trim(expvar.Get("git_branch").String(), `"`),
		GitHash:   strings.Trim(expvar.Get("git_hash").String(), `"`),
		StartAt:   startRuntime,
	}, nil
}

type getMetricsAPIOutput struct {
	RealTimeRequests int64  `json:"real_time_requests"` // 实时请求数
	TotalRequests    int64  `json:"total_requests"`     // 总请求数
	TotalResponses   int64  `json:"total_responses"`    // 总响应数
	RequestTop       []KV   `json:"request_top"`        // 请求TOP
	StatusCodeTop    []KV   `json:"status_code_top"`    // 状态码TOP
	Goroutines       any    `json:"goroutines"`         // 协程数量
	NumGC            uint32 `json:"num_gc"`             // gc 次数
	SysAlloc         uint64 `json:"sys_alloc"`          // 内存占用
	StartAt          string `json:"start_at"`           // 运行时间
}

func (uc *Usecase) getMetricsAPI(_ *gin.Context, _ *struct{}) (*getMetricsAPIOutput, error) {
	req := expvar.Get("request").(*expvar.Int).Value()
	reqs := expvar.Get("requests").(*expvar.Int).Value()
	resps := expvar.Get("responses").(*expvar.Int).Value()
	urls := expvar.Get(`requestURLs`).(*expvar.Map)
	status := expvar.Get(`statusCodes`).(*expvar.Map)
	u := sortExpvarMap(urls, 15)
	s := sortExpvarMap(status, 15)
	g := expvar.Get("goroutine_num").(expvar.Func)

	var stats runtime.MemStats
	runtime.ReadMemStats(&stats)

	return &getMetricsAPIOutput{
		RealTimeRequests: req,
		TotalRequests:    reqs,
		TotalResponses:   resps,
		RequestTop:       u,
		StatusCodeTop:    s,
		Goroutines:       g(),
		NumGC:            stats.NumGC,
		SysAlloc:         stats.Sys,
		StartAt:          startRuntime.Format(time.DateTime),
	}, nil
}

type KV struct {
	Key   string
	Value int64
}

func sortExpvarMap(data *expvar.Map, top int) []KV {
	kvs := make([]KV, 0, 8)
	data.Do(func(kv expvar.KeyValue) {
		kvs = append(kvs, KV{
			Key:   kv.Key,
			Value: kv.Value.(*expvar.Int).Value(),
		})
	})

	sort.Slice(kvs, func(i, j int) bool {
		return kvs[i].Value > kvs[j].Value
	})

	idx := top
	if l := len(kvs); l < top {
		idx = len(kvs)
	}
	return kvs[:idx]
}
