package main

import (
	"errors"
	"fmt"
	"math"
)

type minute struct {
	Total, Bad int
	Present    bool
}

// burn requires every expected bucket to be present. This is an offline model,
// not an implementation of Prometheus staleness, rate, or rule timing.
func burn(xs []minute, slo float64) (float64, error) {
	if len(xs) == 0 || slo <= 0 || slo >= 1 || math.IsNaN(slo) {
		return 0, errors.New("invalid input")
	}
	total, bad := 0, 0
	for _, x := range xs {
		if !x.Present {
			return 0, errors.New("missing data")
		}
		if x.Total < 0 || x.Bad < 0 || x.Bad > x.Total {
			return 0, errors.New("invalid counts")
		}
		total += x.Total
		bad += x.Bad
	}
	if total == 0 {
		return 0, errors.New("no traffic")
	}
	return (float64(bad) / float64(total)) / (1 - slo), nil
}
func page(xs []minute) (bool, float64, float64, error) {
	if len(xs) != 60 {
		return false, 0, 0, errors.New("need 60 minutes")
	}
	long, e := burn(xs, .999)
	if e != nil {
		return false, 0, 0, e
	}
	short, e := burn(xs[55:], .999)
	if e != nil {
		return false, 0, 0, e
	}
	return long > 14.4 && short > 14.4, long, short, nil
}
func scenario(total, bad int) []minute {
	xs := make([]minute, 60)
	for i := range xs {
		xs[i] = minute{total, bad, true}
	}
	return xs
}
func main() {
	active := scenario(1000, 20)
	firing, long, short, err := page(active)
	if err != nil {
		panic(err)
	}
	fmt.Printf("ongoing long=%.2f short=%.2f page=%v\n", long, short, firing)
	recovered := scenario(1000, 20)
	for i := 55; i < 60; i++ {
		recovered[i].Bad = 0
	}
	firing, long, short, err = page(recovered)
	if err != nil {
		panic(err)
	}
	fmt.Printf("recovered long=%.2f short=%.2f page=%v\n", long, short, firing)
	_, _, _, err = page(scenario(0, 0))
	fmt.Println("zero_traffic:", err)
	missing := scenario(1000, 0)
	missing[2].Present = false
	_, _, _, err = page(missing)
	fmt.Println("missing:", err)
	fmt.Printf("budget_fraction_at_threshold=%.3f\n", 14.4*1/(30*24.0))
}
