package main

import (
	"errors"
	"fmt"
	"math/big"
	"time"
)

var ErrNoData = errors.New("no eligible events")

func SLI(good, total int64) (*big.Rat, error) {
	if total == 0 && good == 0 {
		return nil, ErrNoData
	}
	if total <= 0 || good < 0 || good > total {
		return nil, errors.New("invalid counts")
	}
	return new(big.Rat).SetFrac64(good, total), nil
}
func Budget(good, total int64, slo *big.Rat) (allowed, remaining, burn *big.Rat, err error) {
	sli, e := SLI(good, total)
	if e != nil {
		return nil, nil, nil, e
	}
	if slo == nil || slo.Sign() <= 0 || slo.Cmp(big.NewRat(1, 1)) >= 0 {
		return nil, nil, nil, errors.New("SLO must be between 0 and 1")
	}
	fraction := new(big.Rat).Sub(big.NewRat(1, 1), slo)
	allowed = new(big.Rat).Mul(new(big.Rat).SetInt64(total), fraction)
	remaining = new(big.Rat).Sub(allowed, new(big.Rat).SetInt64(total-good))
	burn = new(big.Rat).Quo(new(big.Rat).Sub(big.NewRat(1, 1), sli), fraction)
	return allowed, remaining, burn, nil
}
func Downtime(window time.Duration, nines int) (time.Duration, error) {
	if window <= 0 || nines < 1 || nines > 9 {
		return 0, errors.New("invalid window or nines")
	}
	denom := int64(1)
	for range nines {
		denom *= 10
	}
	return window / time.Duration(denom), nil
}
func main() {
	for _, n := range []int{2, 3, 4, 5} {
		d30, _ := Downtime(30*24*time.Hour, n)
		d365, _ := Downtime(365*24*time.Hour, n)
		fmt.Printf("%d nines: 30d=%s 365d=%s\n", n, d30, d365)
	}
	slo := big.NewRat(999, 1000)
	a, r, b, err := Budget(999500, 1000000, slo)
	if err != nil {
		panic(err)
	}
	sli, _ := SLI(999500, 1000000)
	fmt.Println("SLI:", sli.FloatString(6), "allowed:", a.RatString(), "remaining:", r.RatString(), "burn:", b.FloatString(2))
	_, err = SLI(0, 0)
	fmt.Println("empty window:", err)
	combined, _ := SLI(999+5, 1000+10)
	fmt.Println("weighted SLI:", combined.FloatString(6))
	fmt.Printf("14.4x for 1h consumes %.1f%% of 30d time budget\n", 14.4/720*100)
}
