package main

import (
	"errors"
	"fmt"
	"sync"
)

type Event struct {
	ID     string
	Amount int
}
type Store struct {
	mu      sync.Mutex
	balance int
	seen    map[string]int
}

// Apply models one atomic transaction using a mutex. It has no durability.
func (s *Store) Apply(e Event, failBeforeCommit bool) (bool, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if old, ok := s.seen[e.ID]; ok {
		if old != e.Amount {
			return false, errors.New("event ID reused with different payload")
		}
		return false, nil
	}
	if e.ID == "" || e.Amount <= 0 {
		return false, errors.New("invalid event")
	}
	if failBeforeCommit {
		return false, errors.New("transaction aborted")
	}
	if s.seen == nil {
		s.seen = make(map[string]int)
	}
	s.balance += e.Amount
	s.seen[e.ID] = e.Amount
	return true, nil
}

func unsafeReplay() int {
	balance := 0
	balance += 100 // business commit; crash before acknowledgement
	balance += 100 // redelivery repeats the business effect
	return balance
}

func route(permanent bool, attempt int) string {
	if permanent || attempt >= 3 {
		return "quarantine"
	}
	return "retry-later"
}

type Scheduled struct {
	ID  string
	Due int
}

func release(now int, pending []Scheduled) (ready []string, rest []Scheduled) {
	for _, m := range pending {
		if m.Due <= now {
			ready = append(ready, m.ID)
		} else {
			rest = append(rest, m)
		}
	}
	return
}

func demo() {
	fmt.Println("unsafe balance:", unsafeReplay())
	s := new(Store)
	inserted, err := s.Apply(Event{"e1", 100}, false)
	fmt.Println("first:", inserted, err)
	inserted, err = s.Apply(Event{"e1", 100}, false)
	fmt.Println("redelivery:", inserted, err)
	_, err = s.Apply(Event{"e1", 200}, false)
	fmt.Println("conflict:", err)
	_, err = s.Apply(Event{"e2", 50}, true)
	fmt.Println("failed transaction:", err)
	fmt.Println("safe balance:", s.balance)
	fmt.Println("temporary attempt 1:", route(false, 1))
	fmt.Println("temporary attempt 3:", route(false, 3))
	fmt.Println("invalid payload:", route(true, 1))
	pending := []Scheduled{{"cancel-unpaid", 5}}
	ready, pending := release(4, pending)
	fmt.Println("t=4 ready:", ready)
	ready, _ = release(8, pending)
	fmt.Println("t=8 ready:", ready)
	backlog, incoming, capacity := 6000, 100, 150
	fmt.Println("drain seconds:", backlog/(capacity-incoming))
}

func main() { demo() }
