package main

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

type ReserveRequest struct {
	OrderID  string
	Quantity int
}
type Inventory interface {
	Reserve(context.Context, ReserveRequest) error
}
type Stock struct {
	mu           sync.Mutex
	available    int
	reservations map[string]int
}

func (s *Stock) Reserve(ctx context.Context, r ReserveRequest) error {
	if err := ctx.Err(); err != nil {
		return err
	}
	if r.OrderID == "" || r.Quantity <= 0 {
		return errors.New("invalid reservation")
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	if n, ok := s.reservations[r.OrderID]; ok {
		if n != r.Quantity {
			return errors.New("id conflict")
		}
		return nil
	}
	if r.Quantity > s.available {
		return errors.New("out of stock")
	}
	s.available -= r.Quantity
	s.reservations[r.OrderID] = r.Quantity
	return nil
}

type OrderService struct{ inventory Inventory }

func (o OrderService) Place(ctx context.Context, id string, n int) error {
	return o.inventory.Reserve(ctx, ReserveRequest{id, n})
}
func main() {
	s := &Stock{available: 3, reservations: map[string]int{}}
	o := OrderService{s}
	fmt.Println("first:", o.Place(context.Background(), "o1", 2))
	fmt.Println("same request:", o.Place(context.Background(), "o1", 2))
	fmt.Println("changed request:", o.Place(context.Background(), "o1", 1))
	fmt.Println("stock:", s.available)
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	fmt.Println("cancelled:", o.Place(ctx, "o2", 1))
}
