package middleware

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

	"github.com/edu-cloud/api-gateway/internal/observability"
	"github.com/gin-gonic/gin"
	"github.com/sony/gobreaker/v2"
)

// CircuitBreaker 返回针对指定服务的熔断中间件（W8 裁决：保持共享 downstream）。
//
// 基于 gobreaker v2 实现：
//   - Interval=5s：CLOSED 状态下的统计窗口
//   - ReadyToTrip：错误率 > 50% 触发 OPEN
//   - Timeout=30s：OPEN 持续 30 秒后转 HALF_OPEN
//   - MaxRequests=1：HALF_OPEN 仅允许 1 个探测请求
//
// 仅当下游返回 5xx 视为失败；4xx 与 2xx 不计入熔断。
// 熔断打开或半开探测名额已满时返回 503 + ActionState 信封（W1/W2 裁决）：
//
//	{"success":false,"error":{"code":"GW_CIRCUIT_OPEN","message":"..."},"retry_after":30}
func CircuitBreaker(serviceName string) gin.HandlerFunc {
	cb := gobreaker.NewCircuitBreaker[struct{}](gobreaker.Settings{
		Name:        serviceName,
		MaxRequests: 1,
		Interval:    5 * time.Second,
		Timeout:     30 * time.Second,
		ReadyToTrip: func(counts gobreaker.Counts) bool {
			// 请求数为 0 时不触发，避免除零
			if counts.Requests == 0 {
				return false
			}
			// 错误率 > 50%
			return counts.TotalFailures*2 > counts.Requests
		},
		OnStateChange: func(name string, from, to gobreaker.State) {
			observability.SetCircuitBreakerState(name, from.String(), 0)
			observability.SetCircuitBreakerState(name, to.String(), 1)
			slog.Info("circuit breaker state changed",
				"service", name,
				"from", from.String(),
				"to", to.String(),
			)
		},
	})

	return func(c *gin.Context) {
		_, err := cb.Execute(func() (struct{}, error) {
			c.Next()
			// 下游 5xx 视为熔断失败
			if c.Writer.Status() >= 500 {
				return struct{}{}, errors.New("downstream_error")
			}
			return struct{}{}, nil
		})

		if err != nil {
			// 熔断打开或半开探测名额已满：返回 503
			if errors.Is(err, gobreaker.ErrOpenState) || errors.Is(err, gobreaker.ErrTooManyRequests) {
				c.Header("Retry-After", "30")
				c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{
					"success": false,
					"error": gin.H{
						"code":    "GW_CIRCUIT_OPEN",
						"message": "upstream circuit breaker open",
					},
					"retry_after": 30,
				})
				return
			}
			// 其他情况（下游已写 5xx 响应）：响应已写入，不覆盖
			return
		}
	}
}
