package orchestrator_test

import (
	"context"
	"path/filepath"
	"sync"
	"testing"
	"time"

	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/event"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/orchestrator"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/org"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/runtime"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/runtime/scripted"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/store"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/transport"
)

// TestSpawnRoleIsIdempotent guards the contract Master.delegate and the
// dynamic-spawn fulfiller (V33) rely on: calling SpawnRole twice for the
// same role returns the already-spawned agent rather than creating a new
// row. Without this every retry would spawn another agent.
func TestSpawnRoleIsIdempotent(t *testing.T) {
	tmp := t.TempDir()
	st, err := store.Open(filepath.Join(tmp, "harness.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer st.Close()
	bus := event.NewBus(st)
	q := transport.New(st, bus)
	orch := orchestrator.New(st, bus)
	rt := scripted.New()
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	def := &org.Definition{
		Name: "idemp", Version: 1,
		Roles: []org.Role{
			{ID: "master", Provider: "scripted", DelegatesTo: []string{"worker"}, Tools: []string{}},
			{ID: "worker", Provider: "scripted", Tools: []string{"introspect"}},
		},
		Teams: []org.Team{{ID: "t", Roster: []string{"master", "worker"}}},
	}
	runner := orchestrator.NewOrgRunner(orch, q, map[string]runtime.Runtime{"default": rt, "scripted": rt}, def)
	_ = orch.CreateRun(ctx, "run-idemp", "spawn-role idempotency")

	first, err := runner.SpawnRole(ctx, "run-idemp", "worker")
	if err != nil {
		t.Fatalf("first SpawnRole: %v", err)
	}
	if first == nil {
		t.Fatalf("first SpawnRole returned nil")
	}
	second, err := runner.SpawnRole(ctx, "run-idemp", "worker")
	if err != nil {
		t.Fatalf("second SpawnRole: %v", err)
	}
	if second != first {
		t.Errorf("SpawnRole not idempotent — second call produced a different Agent pointer")
	}

	// And the DB has exactly one worker row for this run.
	var n int
	_ = st.DB().QueryRow(`SELECT COUNT(*) FROM agents WHERE run_id='run-idemp' AND role='worker'`).Scan(&n)
	if n != 1 {
		t.Errorf("worker agent rows = %d, want 1 (duplicate spawn)", n)
	}

	// Unknown role returns an error, not a panic.
	if _, err := runner.SpawnRole(ctx, "run-idemp", "nonexistent"); err == nil {
		t.Errorf("SpawnRole(unknown role) returned nil error")
	}
}

// TestSpawnRoleConcurrent is the V79 regression: N goroutines calling
// SpawnRole for the same role concurrently must produce exactly one agent
// (one DB row, one runtime entry), not N. Without the inflight gate, every
// caller passed the cache check and started a duplicate spawn pipeline.
func TestSpawnRoleConcurrent(t *testing.T) {
	tmp := t.TempDir()
	st, err := store.Open(filepath.Join(tmp, "harness.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer st.Close()
	bus := event.NewBus(st)
	q := transport.New(st, bus)
	orch := orchestrator.New(st, bus)
	rt := scripted.New()
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	def := &org.Definition{
		Name: "race", Version: 1,
		Roles: []org.Role{
			{ID: "master", Provider: "scripted", DelegatesTo: []string{"worker"}},
			{ID: "worker", Provider: "scripted", Tools: []string{"introspect"}},
		},
		Teams: []org.Team{{ID: "t", Roster: []string{"master", "worker"}}},
	}
	runner := orchestrator.NewOrgRunner(orch, q, map[string]runtime.Runtime{"default": rt, "scripted": rt}, def)
	_ = orch.CreateRun(ctx, "run-race", "spawn-role race")

	const N = 16
	var wg sync.WaitGroup
	agentSet := sync.Map{}
	errs := make(chan error, N)
	for i := 0; i < N; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			ag, err := runner.SpawnRole(ctx, "run-race", "worker")
			if err != nil {
				errs <- err
				return
			}
			agentSet.Store(ag.ID, true)
		}()
	}
	wg.Wait()
	close(errs)
	for err := range errs {
		t.Errorf("concurrent SpawnRole: %v", err)
	}

	// Exactly one agent id should have been returned (all callers got
	// the same Agent).
	count := 0
	agentSet.Range(func(_, _ any) bool { count++; return true })
	if count != 1 {
		t.Errorf("got %d distinct agents from %d concurrent SpawnRole calls; want 1", count, N)
	}
	// And exactly one DB row.
	var rows int
	_ = st.DB().QueryRow(`SELECT COUNT(*) FROM agents WHERE run_id='run-race' AND role='worker'`).Scan(&rows)
	if rows != 1 {
		t.Errorf("worker agents.row count = %d; want 1 (duplicate spawn)", rows)
	}
}
