package middleware

import (
	"errors"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/sony/gobreaker/v2"
)

// newTestCircuitBreaker 构造与生产 CircuitBreaker 中间件相同语义、但可配置时序参数的版本，
// 用于在测试中加速 CLOSED → OPEN → HALF_OPEN → CLOSED 状态机验证。
func newTestCircuitBreaker(t *testing.T, interval, timeout time.Duration) (gin.HandlerFunc, *gobreaker.CircuitBreaker[struct{}]) {
	t.Helper()
	cb := gobreaker.NewCircuitBreaker[struct{}](gobreaker.Settings{
		Name:        "test-svc",
		MaxRequests: 1,
		Interval:    interval,
		Timeout:     timeout,
		ReadyToTrip: func(counts gobreaker.Counts) bool {
			if counts.Requests == 0 {
				return false
			}
			return counts.TotalFailures*2 > counts.Requests
		},
	})
	return func(c *gin.Context) {
		_, err := cb.Execute(func() (struct{}, error) {
			c.Next()
			if c.Writer.Status() >= 500 {
				return struct{}{}, errors.New("downstream_error")
			}
			return struct{}{}, nil
		})
		if err != nil {
			if errors.Is(err, gobreaker.ErrOpenState) || errors.Is(err, gobreaker.ErrTooManyRequests) {
				c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{
					"error":       "circuit_open",
					"retry_after": 30,
				})
				return
			}
			return
		}
	}, cb
}

func setupRouter(cbMW gin.HandlerFunc, downstreamStatus int) *gin.Engine {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(cbMW)
	r.GET("/test", func(c *gin.Context) {
		c.Status(downstreamStatus)
	})
	return r
}

// TestCircuitBreaker_ClosedToOpen 验证 5 秒窗口内错误率 > 50% 触发 CLOSED → OPEN。
// ReadyToTrip 逻辑：TotalFailures*2 > Requests。
// 第 1 次 5xx 后（Requests=1, Failures=1, 1*2>1）即触发；第 2 次请求被熔断直接返回 503。
func TestCircuitBreaker_ClosedToOpen(t *testing.T) {
	mw, cb := newTestCircuitBreaker(t, time.Second, 30*time.Second)
	r := setupRouter(mw, http.StatusInternalServerError)

	// 第 1 次 5xx：放行到下游，触发 ReadyToTrip
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	r.ServeHTTP(w, req)
	if w.Code != http.StatusInternalServerError {
		t.Fatalf("第 1 次请求期望 500，实际 %d", w.Code)
	}

	// 第 2 次请求应触发熔断：返回 503 + circuit_open
	w = httptest.NewRecorder()
	req = httptest.NewRequest(http.MethodGet, "/test", nil)
	r.ServeHTTP(w, req)
	if w.Code != http.StatusServiceUnavailable {
		t.Fatalf("熔断后应返回 503，实际 %d", w.Code)
	}
	if !strings.Contains(w.Body.String(), "circuit_open") {
		t.Fatalf("响应体应包含 circuit_open，实际 %s", w.Body.String())
	}
	if cb.State() != gobreaker.StateOpen {
		t.Fatalf("CB 状态应为 OPEN，实际 %s", cb.State())
	}
}

// TestCircuitBreaker_OpenToHalfOpen 验证 OPEN 状态经过 Timeout 后转 HALF_OPEN，允许 1 个探测请求。
// 注意：HALF_OPEN 状态只在探测请求执行期间可见；探测完成后（无论成功失败）状态会立刻转 CLOSED 或回 OPEN。
// 因此本测试通过行为验证：OPEN 期间请求被拒（503），Timeout 后请求放行到下游（500）证明进入了 HALF_OPEN。
func TestCircuitBreaker_OpenToHalfOpen(t *testing.T) {
	mw, cb := newTestCircuitBreaker(t, time.Second, 100*time.Millisecond)
	r := setupRouter(mw, http.StatusInternalServerError)

	// 触发熔断（1 次 5xx 即可）
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	r.ServeHTTP(w, req)
	if cb.State() != gobreaker.StateOpen {
		t.Fatalf("预置条件失败：CB 应为 OPEN，实际 %s", cb.State())
	}

	// 立即再次请求：应被熔断拒绝（503）
	w = httptest.NewRecorder()
	req = httptest.NewRequest(http.MethodGet, "/test", nil)
	r.ServeHTTP(w, req)
	if w.Code != http.StatusServiceUnavailable {
		t.Fatalf("OPEN 期间请求应被拒（503），实际 %d", w.Code)
	}

	// 等待 Timeout + 少量缓冲 → 状态转为 HALF_OPEN
	time.Sleep(150 * time.Millisecond)

	// 探测请求应放行到下游（返回 500，而非 503）
	// 证明状态已从 OPEN → HALF_OPEN（否则会返回 503）
	w = httptest.NewRecorder()
	req = httptest.NewRequest(http.MethodGet, "/test", nil)
	r.ServeHTTP(w, req)
	if w.Code != http.StatusInternalServerError {
		t.Fatalf("HALF_OPEN 探测请求应放行到下游（500），实际 %d", w.Code)
	}
	// 探测失败（下游 500）→ 状态回 OPEN
	if cb.State() != gobreaker.StateOpen {
		t.Fatalf("探测失败后 CB 应回 OPEN，实际 %s", cb.State())
	}
}

// TestCircuitBreaker_HalfOpenToClosed 验证 HALF_OPEN 探测成功后转 CLOSED。
func TestCircuitBreaker_HalfOpenToClosed(t *testing.T) {
	mw, cb := newTestCircuitBreaker(t, time.Second, 100*time.Millisecond)
	// 下游成功（200），让探测能成功
	r := setupRouter(mw, http.StatusOK)

	// 先用另一个 router 触发熔断（下游 500）
	badR := setupRouter(mw, http.StatusInternalServerError)
	for i := 0; i < 4; i++ {
		w := httptest.NewRecorder()
		req := httptest.NewRequest(http.MethodGet, "/test", nil)
		badR.ServeHTTP(w, req)
	}
	if cb.State() != gobreaker.StateOpen {
		t.Fatalf("预置条件失败：CB 应为 OPEN，实际 %s", cb.State())
	}

	// 等待 Timeout → HALF_OPEN
	time.Sleep(150 * time.Millisecond)

	// 探测请求（下游 200）→ 成功 → 转 CLOSED
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Fatalf("探测请求应返回 200，实际 %d", w.Code)
	}
	if cb.State() != gobreaker.StateClosed {
		t.Fatalf("探测成功后 CB 应为 CLOSED，实际 %s", cb.State())
	}
}

// TestCircuitBreaker_HalfOpenToOpen 验证 HALF_OPEN 探测失败后转回 OPEN。
func TestCircuitBreaker_HalfOpenToOpen(t *testing.T) {
	mw, cb := newTestCircuitBreaker(t, time.Second, 100*time.Millisecond)
	r := setupRouter(mw, http.StatusInternalServerError)

	// 触发熔断
	for i := 0; i < 4; i++ {
		w := httptest.NewRecorder()
		req := httptest.NewRequest(http.MethodGet, "/test", nil)
		r.ServeHTTP(w, req)
	}
	if cb.State() != gobreaker.StateOpen {
		t.Fatalf("预置条件失败：CB 应为 OPEN，实际 %s", cb.State())
	}

	// 等待 Timeout → HALF_OPEN
	time.Sleep(150 * time.Millisecond)

	// 探测请求（下游 500）→ 失败 → 转 OPEN
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	r.ServeHTTP(w, req)
	if cb.State() != gobreaker.StateOpen {
		t.Fatalf("探测失败后 CB 应重新转 OPEN，实际 %s", cb.State())
	}
}

// TestCircuitBreaker_4xxNotCounted 验证 4xx 响应不计入熔断失败计数。
func TestCircuitBreaker_4xxNotCounted(t *testing.T) {
	var callCount int32
	mw, cb := newTestCircuitBreaker(t, time.Second, 30*time.Second)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	r.Use(mw)
	r.GET("/test", func(c *gin.Context) {
		atomic.AddInt32(&callCount, 1)
		c.Status(http.StatusNotFound) // 404
	})

	// 触发 10 次 404，应不触发熔断
	for i := 0; i < 10; i++ {
		w := httptest.NewRecorder()
		req := httptest.NewRequest(http.MethodGet, "/test", nil)
		r.ServeHTTP(w, req)
		if w.Code != http.StatusNotFound {
			t.Fatalf("请求 %d 应返回 404，实际 %d", i+1, w.Code)
		}
	}

	if cb.State() != gobreaker.StateClosed {
		t.Fatalf("4xx 不应触发熔断，CB 应保持 CLOSED，实际 %s", cb.State())
	}

	// 所有请求应都到达下游（未被熔断短路）
	if got := atomic.LoadInt32(&callCount); got != 10 {
		t.Fatalf("下游应被调用 10 次，实际 %d", got)
	}
}
