package main

import (
	"context"
	"errors"
	"fmt"
	sentinel "github.com/alibaba/sentinel-golang/api"
	"github.com/alibaba/sentinel-golang/core/config"
	"github.com/alibaba/sentinel-golang/core/isolation"
	"github.com/alibaba/sentinel-golang/logging"
	failsafe "github.com/failsafe-go/failsafe-go"
	"github.com/failsafe-go/failsafe-go/retrypolicy"
	"github.com/sony/gobreaker/v2"
	vegeta "github.com/tsenart/vegeta/v12/lib"
	"golang.org/x/time/rate"
	"net/http"
	"net/http/httptest"
	"sync"
	"sync/atomic"
	"time"
)

var transient = errors.New("transient")

func RateDemo() (bool, bool, bool, bool) {
	l := rate.NewLimiter(2, 2)
	now := time.Now()
	a := l.AllowN(now, 1)
	b := l.AllowN(now, 1)
	c := l.AllowN(now, 1)
	d := l.AllowN(now.Add(500*time.Millisecond), 1)
	return a, b, c, d
}
func NewBreaker(timeout time.Duration) *gobreaker.CircuitBreaker[int] {
	return gobreaker.NewCircuitBreaker[int](gobreaker.Settings{Name: "local", Timeout: timeout, MaxRequests: 1, ReadyToTrip: func(c gobreaker.Counts) bool { return c.ConsecutiveFailures >= 2 }})
}
func BreakerDemo() (int, bool, string) {
	cb := NewBreaker(time.Hour)
	calls := 0
	fail := func() (int, error) { calls++; return 0, transient }
	_, _ = cb.Execute(fail)
	_, _ = cb.Execute(fail)
	_, err := cb.Execute(fail)
	return calls, errors.Is(err, gobreaker.ErrOpenState), cb.State().String()
}
func RetryDemo(ctx context.Context) (int, int, error) {
	policy := retrypolicy.NewBuilder[int]().HandleErrors(transient).WithMaxRetries(2).Build()
	calls := 0
	value, err := failsafe.With(policy).WithContext(ctx).GetWithExecution(func(ex failsafe.Execution[int]) (int, error) {
		if err := ex.Context().Err(); err != nil {
			return 0, err
		}
		calls++
		if calls < 3 {
			return 0, transient
		}
		return 42, nil
	})
	return calls, value, err
}

var initOnce sync.Once
var initErr error

func InitSentinel() error {
	initOnce.Do(func() {
		cfg := config.NewDefaultConfig()
		cfg.Sentinel.Log.Logger = logging.NewConsoleLogger()
		cfg.Sentinel.Log.Metric.FlushIntervalSec = 0
		cfg.Sentinel.Stat.System = config.SystemStatConfig{}
		cfg.Sentinel.UseCacheTime = false
		logging.ResetGlobalLoggerLevel(logging.ErrorLevel)
		initErr = sentinel.InitWithConfig(cfg)
	})
	return initErr
}
func SentinelDemo() (bool, bool, error) {
	if err := InitSentinel(); err != nil {
		return false, false, err
	}
	_, err := isolation.LoadRules([]*isolation.Rule{{Resource: "lab", MetricType: isolation.Concurrency, Threshold: 1}})
	if err != nil {
		return false, false, err
	}
	defer isolation.LoadRules(nil)
	first, block := sentinel.Entry("lab")
	if block != nil {
		return false, false, block
	}
	blocked := false
	func() {
		defer first.Exit()
		second, b := sentinel.Entry("lab")
		blocked = b != nil
		if second != nil {
			second.Exit()
		}
	}()
	third, b := sentinel.Entry("lab")
	if b != nil {
		return blocked, false, b
	}
	third.Exit()
	return blocked, true, nil
}
func VegetaDemo() (uint64, float64, int, int) {
	var n atomic.Int64
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if n.Add(1)%2 == 0 {
			http.Error(w, "fixture failure", 500)
			return
		}
		fmt.Fprint(w, "ok")
	}))
	defer srv.Close()
	a := vegeta.NewAttacker(vegeta.Workers(1), vegeta.MaxWorkers(2), vegeta.Timeout(200*time.Millisecond))
	defer a.Stop()
	targets := vegeta.NewStaticTargeter(vegeta.Target{Method: "GET", URL: srv.URL})
	var m vegeta.Metrics
	for res := range a.Attack(targets, vegeta.Rate{Freq: 4, Per: time.Second}, time.Second, "loopback") {
		m.Add(res)
	}
	m.Close()
	return m.Requests, m.Success, m.StatusCodes["200"], m.StatusCodes["500"]
}
func main() {
	a, b, c, d := RateDemo()
	fmt.Println("rate:", a, b, c, d)
	calls, blocked, state := BreakerDemo()
	fmt.Println("breaker calls:", calls, "blocked:", blocked, "state:", state)
	calls, value, err := RetryDemo(context.Background())
	fmt.Println("failsafe attempts:", calls, "value:", value, "error:", err)
	blocked, released, err := SentinelDemo()
	fmt.Println("sentinel blocked:", blocked, "released:", released, "error:", err)
	n, success, ok, fail := VegetaDemo()
	fmt.Printf("vegeta requests=%d success=%.2f status200=%d status500=%d\n", n, success, ok, fail)
}
