package control

import (
	"encoding/json"
	"os"
	"path/filepath"
	"sync"
	"testing"

	"reasonix/internal/agent"
	"reasonix/internal/event"
)

// TestGoalStateWritesAreConcurrencySafe hammers goal-state persistence from many
// goroutines (each GoalStrict builds the JSON under c.mu, then writes off-lock via
// goalWriteMu) while c.mu-guarded reads run concurrently. Under -race this proves
// the new build-under-lock / write-off-lock split has no data race, and that
// goalWriteMu keeps the on-disk file from being torn by interleaved writes.
func TestGoalStateWritesAreConcurrencySafe(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "session.jsonl")
	sess := agent.NewSession("sys")
	exec := agent.New(nil, nil, sess, agent.Options{}, event.Discard)
	c := New(Options{Executor: exec, SessionDir: dir, SessionPath: path, Label: "test"})
	c.SetGoalWithResearchMode("concurrent goal", GoalResearchOn)

	stop := make(chan struct{})
	var readers sync.WaitGroup
	readers.Go(func() {
		for {
			select {
			case <-stop:
				return
			default:
				_ = c.Running()       // takes c.mu
				_ = c.RuntimeStatus() // takes c.mu
				_ = c.Goal()          // takes c.mu
				_ = c.GoalStatus()    // takes c.mu
			}
		}
	})

	var writers sync.WaitGroup
	for w := range 8 {
		writers.Add(1)
		go func(w int) {
			defer writers.Done()
			for i := range 10 {
				c.GoalStrict(i%2 == 0) // build under c.mu, write off-lock
			}
		}(w)
	}
	writers.Wait()
	close(stop)
	readers.Wait()

	// goalWriteMu must have kept the file intact: still valid JSON, goal preserved.
	data, err := os.ReadFile(goalStatePath(path))
	if err != nil {
		t.Fatalf("read goal state: %v", err)
	}
	var state goalState
	if err := json.Unmarshal(data, &state); err != nil {
		t.Fatalf("goal state file torn by concurrent writes: %v\n%s", err, data)
	}
	if state.Goal != "concurrent goal" || state.Status != GoalStatusRunning {
		t.Fatalf("goal state = %+v, want the active goal preserved", state)
	}
}
