package orchestrator_test

import (
	"context"
	"path/filepath"
	"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"
)

// TestEscalateChainReflectsOrgTopology is the V30 regression test. In a
// 3-deep org (master → lead → worker) the worker's Introspect must report
// EscalateTo=lead, NOT EscalateTo=master. The brief: "agents must know how
// they are embedded into the system" — a worker that thinks it escalates
// directly to master would skip the orchestrator-tier responsibility.
func TestEscalateChainReflectsOrgTopology(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: "hier-3", Version: 1,
		SubOrgs: []org.SubOrg{{ID: "frontend", Teams: []string{"fe-team"}, ZoneScope: []string{"client/**/*"}}},
		Roles: []org.Role{
			{ID: "master", Provider: "scripted", DelegatesTo: []string{"lead"}, Tools: []string{"delegate"}},
			{ID: "lead", Provider: "scripted", DelegatesTo: []string{"worker"}, Tools: []string{"send_message"}},
			{ID: "worker", Provider: "scripted", Tools: []string{"introspect"}},
		},
		Teams: []org.Team{{ID: "fe-team", Roster: []string{"lead", "worker"}}},
	}
	runner := orchestrator.NewOrgRunner(orch, q, map[string]runtime.Runtime{"default": rt, "scripted": rt}, def)

	_ = orch.CreateRun(ctx, "run-esc", "escalate chain test")
	if _, err := runner.SpawnAll(ctx, "run-esc"); err != nil {
		t.Fatalf("SpawnAll: %v", err)
	}

	leadID, _ := runner.AgentIDFor("lead")
	workerID, _ := runner.AgentIDFor("worker")

	// Verify the durable parent_agent_id link is set correctly per role.
	var workerParent, leadParent, masterParent string
	_ = st.DB().QueryRow(`SELECT IFNULL(parent_agent_id,'') FROM agents WHERE id=?`, workerID).Scan(&workerParent)
	_ = st.DB().QueryRow(`SELECT IFNULL(parent_agent_id,'') FROM agents WHERE id=?`, leadID).Scan(&leadParent)
	masterID, _ := runner.AgentIDFor("master")
	_ = st.DB().QueryRow(`SELECT IFNULL(parent_agent_id,'') FROM agents WHERE id=?`, masterID).Scan(&masterParent)

	if workerParent != leadID {
		t.Errorf("worker's parent_agent_id = %q, want %q (the lead)", workerParent, leadID)
	}
	if leadParent != masterID {
		t.Errorf("lead's parent_agent_id = %q, want %q (master)", leadParent, masterID)
	}
	if masterParent != "" {
		t.Errorf("master's parent_agent_id = %q, want \"\" (master is root)", masterParent)
	}

	// Now exercise Introspect through the runtime.Agent path. Build an
	// Agent so we can call Introspect(); the runtime adapter doesn't
	// matter for this read-only call.
	ag := runtime.NewAgent(rt, st, bus, q, workerID, "worker", "run-esc", nil, []string{"introspect"}, "scripted", 0)
	emb, err := ag.Introspect(ctx)
	if err != nil {
		t.Fatalf("Introspect: %v", err)
	}
	if emb.EscalateTo != leadID {
		t.Errorf("worker introspect EscalateTo = %q, want %q (the lead, NOT master)", emb.EscalateTo, leadID)
	}
	if emb.ParentAgentID != leadID {
		t.Errorf("worker introspect ParentAgentID = %q, want %q", emb.ParentAgentID, leadID)
	}
}

// TestSpawnAllOrderIndependent is the V89 regression: a yaml that declares
// children BEFORE their parents must still produce correctly-wired
// parent_agent_id values. Before the topo-sort, SpawnAll would have spawned
// children first and they'd end up with parent_agent_id=NULL.
func TestSpawnAllOrderIndependent(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()

	// Intentionally reversed declaration order — child before parent.
	def := &org.Definition{
		Name: "reversed", Version: 1,
		Roles: []org.Role{
			{ID: "worker", Provider: "scripted", Tools: []string{}},
			{ID: "lead", Provider: "scripted", DelegatesTo: []string{"worker"}},
			{ID: "master", Provider: "scripted", DelegatesTo: []string{"lead"}},
		},
		Teams: []org.Team{{ID: "t", Roster: []string{"master", "lead", "worker"}}},
	}
	runner := orchestrator.NewOrgRunner(orch, q, map[string]runtime.Runtime{"default": rt, "scripted": rt}, def)
	_ = orch.CreateRun(ctx, "run-rev", "reverse order")
	if _, err := runner.SpawnAll(ctx, "run-rev"); err != nil {
		t.Fatalf("SpawnAll: %v", err)
	}
	masterID, _ := runner.AgentIDFor("master")
	leadID, _ := runner.AgentIDFor("lead")
	workerID, _ := runner.AgentIDFor("worker")
	var workerParent, leadParent string
	_ = st.DB().QueryRow(`SELECT IFNULL(parent_agent_id,'') FROM agents WHERE id=?`, workerID).Scan(&workerParent)
	_ = st.DB().QueryRow(`SELECT IFNULL(parent_agent_id,'') FROM agents WHERE id=?`, leadID).Scan(&leadParent)
	if workerParent != leadID {
		t.Errorf("worker parent (yaml-reversed) = %q, want %q (the lead)", workerParent, leadID)
	}
	if leadParent != masterID {
		t.Errorf("lead parent (yaml-reversed) = %q, want %q (master)", leadParent, masterID)
	}
}
