package main

import (
	"sync"
	"sync/atomic"
	"testing"
)

func TestEvolution(t *testing.T) {
	s := &Store{History: map[string][]Event{}}
	e1, err := s.Append(0, Event{Task: "t", Kind: "Created", Schema: 1, Name: "old"})
	if err != nil {
		t.Fatal(err)
	}
	e2, err := s.Append(1, Event{Task: "t", Kind: "Renamed", Schema: 2, Title: "new"})
	if err != nil {
		t.Fatal(err)
	}
	p := NewProjection()
	if err = p.Apply(e2); err == nil || len(p.Rows) != 0 {
		t.Fatal("accepted gap")
	}
	for _, e := range []Event{e1, e1, e2, e2} {
		if err = p.Apply(e); err != nil {
			t.Fatal(err)
		}
	}
	if p.Rows["t"] != (State{Title: "new", Version: 2}) {
		t.Fatal(p.Rows)
	}
	bad := e2
	bad.Title = "tampered"
	if err = p.Apply(bad); err == nil {
		t.Fatal("accepted payload conflict")
	}
	bad = e2
	bad.ID = "bad"
	bad.Schema = 99
	if err = p.Apply(bad); err == nil {
		t.Fatal("accepted unknown schema")
	}
	rebuilt, err := rebuild(s.Events("t"))
	if err != nil || rebuilt.Rows["t"] != p.Rows["t"] {
		t.Fatal(err)
	}
	if _, err = s.Append(2, Event{Task: "t", Kind: "Unknown", Schema: 2}); err == nil || len(s.Events("t")) != 2 {
		t.Fatal("invalid append mutated history")
	}
}
func TestOptimisticConcurrency(t *testing.T) {
	s := &Store{History: map[string][]Event{}}
	_, err := s.Append(0, Event{Task: "t", Kind: "Created", Schema: 2, Title: "first"})
	if err != nil {
		t.Fatal(err)
	}
	var success atomic.Int32
	var wg sync.WaitGroup
	for range 10 {
		wg.Go(func() {
			if _, err := s.Append(1, Event{Task: "t", Kind: "Renamed", Schema: 2, Title: "second"}); err == nil {
				success.Add(1)
			}
		})
	}
	wg.Wait()
	if success.Load() != 1 || len(s.Events("t")) != 2 {
		t.Fatal(success.Load())
	}
}
func TestRebuildDoesNotNotify(t *testing.T) {
	es := []Event{{ID: "t:1", Task: "t", Kind: "Created", Seq: 1, Schema: 1, Name: "old"}, {ID: "t:2", Task: "t", Kind: "Completed", Seq: 2, Schema: 2}}
	delivered := map[string]bool{}
	if notifyCompleted(es, delivered) != 1 {
		t.Fatal("missing live effect")
	}
	if _, err := rebuild(es); err != nil {
		t.Fatal(err)
	}
	if notifyCompleted(es, delivered) != 0 {
		t.Fatal("duplicate effect")
	}
}

// Counterexample: applying a projection before remembering progress duplicates a delta.
func TestSplitCommitCounterexample(t *testing.T) {
	count := 0
	seen := false
	count++ // crash before seen=true
	if !seen {
		count++
		seen = true
	}
	if count != 2 || !seen {
		t.Fatal(count, seen)
	}
	t.Logf("broken split commit count=%d", count)
}
