package middleware

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/1Panel-dev/1Panel/backend/app/model"
	"github.com/1Panel-dev/1Panel/backend/app/service"
	"github.com/1Panel-dev/1Panel/backend/global"
	"github.com/gin-gonic/gin"
)

func OperationRecord() gin.HandlerFunc {
	return func(c *gin.Context) {
		var body []byte
		if strings.Contains(c.Request.URL.Path, "search") {
			c.Next()
			return
		}

		if c.Request.Method == http.MethodGet {
			query := c.Request.URL.RawQuery
			query, _ = url.QueryUnescape(query)
			split := strings.Split(query, "&")
			m := make(map[string]string)
			for _, v := range split {
				kv := strings.Split(v, "=")
				if len(kv) == 2 {
					m[kv[0]] = kv[1]
				}
			}
			body, _ = json.Marshal(&m)
		} else {
			var err error
			body, err = ioutil.ReadAll(c.Request.Body)
			if err != nil {
				global.LOG.Errorf("read body from request failed, err: %v", err)
			} else {
				c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
			}
		}
		pathInfo := loadLogInfo(c.Request.URL.Path)

		record := model.OperationLog{
			Group:     pathInfo.group,
			Source:    pathInfo.source,
			Action:    pathInfo.action,
			IP:        c.ClientIP(),
			Method:    c.Request.Method,
			Path:      c.Request.URL.Path,
			UserAgent: c.Request.UserAgent(),
			Body:      string(body),
		}

		writer := responseBodyWriter{
			ResponseWriter: c.Writer,
			body:           &bytes.Buffer{},
		}
		c.Writer = writer
		now := time.Now()

		c.Next()

		latency := time.Since(now)
		record.Latency = latency
		record.Resp = writer.body.String()

		if err := service.NewILogService().CreateOperationLog(record); err != nil {
			global.LOG.Errorf("create operation record failed, err: %v", err)
		}
	}
}

type responseBodyWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (r responseBodyWriter) Write(b []byte) (int, error) {
	r.body.Write(b)
	return r.ResponseWriter.Write(b)
}

type pathInfo struct {
	group  string
	source string
	action string
}

func loadLogInfo(path string) pathInfo {
	path = strings.ReplaceAll(path, "/api/v1", "")
	if !strings.Contains(path, "/") {
		return pathInfo{}
	}
	pathArrys := strings.Split(path, "/")
	if len(pathArrys) < 2 {
		return pathInfo{}
	}
	if len(pathArrys) == 2 {
		return pathInfo{group: pathArrys[1]}
	}
	if len(pathArrys) == 3 {
		return pathInfo{group: pathArrys[1], source: pathArrys[2]}
	}
	return pathInfo{group: pathArrys[1], source: pathArrys[2], action: pathArrys[3]}
}
