package middleware

import (
	"compress/gzip"
	"io"
	"net/http"
	"strings"
	"sync"

	"github.com/gin-gonic/gin"
)

// gzipMinLength 小于此字节数的响应不压缩（避免小响应压缩反而增大体积）
const gzipMinLength = 1024

// gzipPool 复用 gzip.Writer，减少 GC 压力
var gzipPool = sync.Pool{
	New: func() interface{} {
		w, _ := gzip.NewWriterLevel(io.Discard, gzip.BestSpeed)
		return w
	},
}

// gzipResponseWriter 包装 gin.ResponseWriter，按需启用 gzip 压缩。
// 所有写入先缓冲；第一次超过阈值时决定是否压缩。
type gzipResponseWriter struct {
	gin.ResponseWriter
	gz        *gzip.Writer
	buf       []byte
	threshold int
	decided   bool // 已决定是否压缩
}

func (g *gzipResponseWriter) Write(data []byte) (int, error) {
	if g.decided {
		if g.gz != nil {
			return g.gz.Write(data)
		}
		return g.ResponseWriter.Write(data)
	}

	// 积累数据
	g.buf = append(g.buf, data...)
	if len(g.buf) >= g.threshold {
		return len(data), g.decide()
	}
	return len(data), nil
}

func (g *gzipResponseWriter) WriteString(s string) (int, error) {
	return g.Write([]byte(s))
}

// decide 根据已缓冲内容和 Content-Type 决定是否压缩，并写出缓冲数据
func (g *gzipResponseWriter) decide() error {
	g.decided = true

	ct := g.ResponseWriter.Header().Get("Content-Type")
	if g.gz != nil && shouldCompress(ct) {
		// 启用 gzip
		g.ResponseWriter.Header().Set("Content-Encoding", "gzip")
		g.ResponseWriter.Header().Set("Vary", "Accept-Encoding")
		g.ResponseWriter.Header().Del("Content-Length")
		g.gz.Reset(g.ResponseWriter)
		if len(g.buf) > 0 {
			_, err := g.gz.Write(g.buf)
			g.buf = nil
			return err
		}
	} else {
		// 不压缩：回收 gzip.Writer
		if g.gz != nil {
			gzipPool.Put(g.gz)
			g.gz = nil
		}
		if len(g.buf) > 0 {
			_, err := g.ResponseWriter.Write(g.buf)
			g.buf = nil
			return err
		}
	}
	g.buf = nil
	return nil
}

// finalize 在请求处理完毕后刷出剩余缓冲数据并关闭 gzip.Writer
func (g *gzipResponseWriter) finalize() {
	if !g.decided {
		// 响应体小于阈值，直接透传（不压缩）
		g.decided = true
		if g.gz != nil {
			gzipPool.Put(g.gz)
			g.gz = nil
		}
		if len(g.buf) > 0 {
			_, _ = g.ResponseWriter.Write(g.buf)
			g.buf = nil
		}
		return
	}

	if g.gz != nil {
		_ = g.gz.Flush()
		_ = g.gz.Close()
		gzipPool.Put(g.gz)
		g.gz = nil
	}
}

// shouldCompress 根据 Content-Type 判断是否值得压缩（二进制流不压缩）
func shouldCompress(contentType string) bool {
	ct := strings.ToLower(strings.SplitN(contentType, ";", 2)[0])
	switch ct {
	case "application/json",
		"application/javascript",
		"text/html",
		"text/plain",
		"text/css",
		"text/xml",
		"application/xml",
		"application/x-www-form-urlencoded":
		return true
	}
	return false
}

// GzipMiddleware 对 JSON/文本类响应启用 GZIP 压缩。
//
// 仅在满足以下条件时压缩：
//   - 客户端发送了 Accept-Encoding: gzip
//   - 响应 Content-Type 为 JSON/文本类
//   - 响应体超过 gzipMinLength（默认 1 KiB）
//
// 其余情况透传，不影响性能。
func GzipMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 客户端不接受 gzip 则跳过
		if !strings.Contains(c.GetHeader("Accept-Encoding"), "gzip") {
			c.Next()
			return
		}

		gz := gzipPool.Get().(*gzip.Writer)

		grw := &gzipResponseWriter{
			ResponseWriter: c.Writer,
			gz:             gz,
			threshold:      gzipMinLength,
		}

		c.Writer = grw

		defer func() {
			grw.finalize()
			c.Writer = grw.ResponseWriter
		}()

		c.Next()
	}
}

// Ensure gzipResponseWriter implements http.Hijacker forwarding (needed by some WebSocket libs)
var _ http.ResponseWriter = (*gzipResponseWriter)(nil)
