package main

import (
	"errors"
	"fmt"
)

type Basket struct {
	ID         string
	Version    int
	RAMGiB     int
	StorageGiB int
}

type ErrorCode string

const staleView ErrorCode = "STALE_VIEW"

type TransitionError struct {
	Code     ErrorCode
	Actor    string
	Expected int
	Actual   int
}

func (e *TransitionError) Error() string {
	return fmt.Sprintf(
		"%s: actor=%s expected_version=%d actual_version=%d",
		e.Code,
		e.Actor,
		e.Expected,
		e.Actual,
	)
}

func main() {
	initial := Basket{
		ID:         "basket-42",
		Version:    7,
		RAMGiB:     16,
		StorageGiB: 512,
	}

	fmt.Println("VERSIONED STATE TRANSITION COUNTEREXAMPLE")
	fmt.Println()
	fmt.Printf("Initial state: %s\n", describe(initial))
	fmt.Println("Actor A and actor B both read basket v7.")
	fmt.Println("A intends to upgrade RAM to 32 GiB.")
	fmt.Println("B intends to upgrade storage to 1024 GiB.")
	fmt.Println()

	runNaive(initial)
	fmt.Println()
	runGuarded(initial)
	fmt.Println()

	fmt.Println("SCOPE")
	fmt.Println("This deterministic, single-process model demonstrates one stale-read interleaving.")
	fmt.Println("The guarded result depends on expected_version validation and the state write being atomic.")
	fmt.Println("It does not prove distributed consensus, durability, availability, or correctness when that check-and-write is split across systems.")
}

func runNaive(initial Basket) {
	fmt.Println("NAIVE: FULL-SNAPSHOT LAST-WRITE-WINS")

	aSnapshot := initial
	bSnapshot := initial

	aSnapshot.RAMGiB = 32
	bSnapshot.StorageGiB = 1024

	current := initial
	current = replaceSnapshot(current, aSnapshot)
	fmt.Printf("A writes its v7 snapshot       -> %s\n", describe(current))

	current = replaceSnapshot(current, bSnapshot)
	fmt.Printf("B writes its stale v7 snapshot -> %s\n", describe(current))

	require(current.Version == 9, "naive schedule should contain two accepted writes")
	require(current.RAMGiB == 16, "naive schedule should reproduce A's lost RAM update")
	require(current.StorageGiB == 1024, "naive schedule should retain B's storage update")

	fmt.Println("Observed: B's write succeeded, but silently restored RAM from 32 GiB to 16 GiB.")
	fmt.Println("Counterexample reproduced: two well-formed snapshots accepted by a blind replacement API did not preserve both intents.")
}

func runGuarded(initial Basket) {
	fmt.Println("GUARDED: ATOMIC expected_version")

	aView := initial
	bView := initial
	aProposal := aView
	bProposal := bView
	aProposal.RAMGiB = 32
	bProposal.StorageGiB = 1024
	current := initial

	var err error
	current, err = replaceSnapshotIfVersion(current, "A", aView.Version, aProposal)
	require(err == nil, "A's transition from the current version should succeed")
	fmt.Printf("A applies expected_version=7   -> %s\n", describe(current))

	beforeRejectedWrite := current
	current, err = replaceSnapshotIfVersion(current, "B", bView.Version, bProposal)

	var transitionErr *TransitionError
	require(errors.As(err, &transitionErr), "B's stale transition should return a typed error")
	require(transitionErr.Code == staleView, "B's error code should be STALE_VIEW")
	require(current == beforeRejectedWrite, "a rejected stale transition must not mutate state")
	fmt.Printf("B applies expected_version=7   -> %s\n", transitionErr)
	fmt.Printf("State after rejection          -> %s\n", describe(current))

	bRefreshedView := current
	bRefreshedProposal := bRefreshedView
	bRefreshedProposal.StorageGiB = 1024
	fmt.Printf("B refreshes and replans on v%d.\n", bRefreshedView.Version)
	current, err = replaceSnapshotIfVersion(
		current,
		"B",
		bRefreshedView.Version,
		bRefreshedProposal,
	)
	require(err == nil, "B's replanned transition from the current version should succeed")
	fmt.Printf("B applies expected_version=8   -> %s\n", describe(current))

	require(current.Version == 9, "guarded schedule should finish at v9")
	require(current.RAMGiB == 32, "guarded schedule must preserve A's RAM intent")
	require(current.StorageGiB == 1024, "guarded schedule must apply B's storage intent")

	fmt.Println("Invariants pass: v9 contains both intended changes and no stale write was applied.")
}

// replaceSnapshot deliberately ignores the snapshot's source version. The
// server assigns a new version and lets the latest full snapshot replace all
// fields, reproducing last-write-wins behavior.
func replaceSnapshot(current, snapshot Basket) Basket {
	snapshot.Version = current.Version + 1
	return snapshot
}

// replaceSnapshotIfVersion models one atomic compare-and-replace operation. A
// real implementation must enforce the version predicate and replacement in
// the same transactional boundary.
func replaceSnapshotIfVersion(
	current Basket,
	actor string,
	expectedVersion int,
	proposal Basket,
) (Basket, error) {
	if current.Version != expectedVersion {
		return current, &TransitionError{
			Code:     staleView,
			Actor:    actor,
			Expected: expectedVersion,
			Actual:   current.Version,
		}
	}

	next := proposal
	next.Version = current.Version + 1
	return next, nil
}

func describe(b Basket) string {
	return fmt.Sprintf(
		"%s v%d {RAM=%d GiB, storage=%d GiB}",
		b.ID,
		b.Version,
		b.RAMGiB,
		b.StorageGiB,
	)
}

func require(condition bool, message string) {
	if !condition {
		panic("invariant failed: " + message)
	}
}
