package control

import (
	"context"
	"strings"
	"sync"
	"testing"

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

// TestCompactRefusedWhileRunning locks in the same guard Rewind/Branch have:
// the run loop is the only sanctioned writer of the live session during a
// turn, so a manual compact must be refused instead of rewriting the log
// underneath it.
func TestCompactRefusedWhileRunning(t *testing.T) {
	sess := agent.NewSession("sys")
	sess.Add(provider.Message{Role: provider.RoleUser, Content: "hi"})
	exec := agent.New(nil, nil, sess, agent.Options{}, event.Discard)
	c := New(Options{
		Executor:   exec,
		SessionDir: t.TempDir(),
		Label:      "test",
		Sink:       event.Discard,
	})

	c.mu.Lock()
	c.running = true
	c.mu.Unlock()

	err := c.Compact(context.Background(), "")
	if err == nil {
		t.Fatal("Compact while running should be refused")
	}
	if !strings.Contains(err.Error(), "cannot compact") {
		t.Fatalf("err = %v, want 'cannot compact' guard error", err)
	}
}

// TestRewindConcurrentWithHistoryReads exercises the conversation-rewind
// truncation against parallel History/CheckpointHasBoundary readers; before
// Rewind switched to Session.Snapshot/Replace the bare
// `s.Messages = s.Messages[:boundary]` write raced them (caught by -race).
func TestRewindConcurrentWithHistoryReads(t *testing.T) {
	c, ag, _ := runTwoTurns(t)

	c.checkpoints.mu.Lock()
	lastTurn := c.checkpoints.turn - 1
	c.checkpoints.mu.Unlock()

	stop := make(chan struct{})
	var wg sync.WaitGroup
	for range 4 {
		wg.Go(func() {
			for {
				select {
				case <-stop:
					return
				default:
					_ = c.History()
					_ = c.CheckpointHasBoundary(lastTurn)
				}
			}
		})
	}

	err := c.Rewind(lastTurn, RewindConversation)
	close(stop)
	wg.Wait()
	if err != nil {
		t.Fatalf("Rewind: %v", err)
	}

	// The rewind truncated the log back to the last turn's boundary; History
	// must still serve a consistent snapshot afterwards.
	if got, want := ag.Session().Len(), 3; got != want { // sys + first prompt/answer
		t.Fatalf("messages after rewind = %d, want %d", got, want)
	}
}
