package inbox

import (
	"path/filepath"
	"testing"
	"time"
)

type watchCommandResult struct {
	stdout string
	stderr string
	exit   int
}

func TestWatchWakesOnMatchingThread(t *testing.T) {
	t.Parallel()

	dbPath := filepath.Join(t.TempDir(), "coord.db")
	runInboxCommand(t, "--db", dbPath, "--json", "init")

	watchCh := make(chan watchCommandResult, 1)
	go func() {
		stdout, stderr, exitCode := executeInboxCommand(
			"--db", dbPath,
			"--json",
			"watch",
			"--agent", "worker-d",
			"--status", "pending",
			"--timeout-seconds", "2",
		)
		watchCh <- watchCommandResult{stdout: stdout, stderr: stderr, exit: exitCode}
	}()

	time.Sleep(200 * time.Millisecond)

	sendOut := runInboxCommand(
		t,
		"--db", dbPath,
		"--json",
		"send",
		"--from", "leader",
		"--to", "worker-d",
		"--subject", "Build admin editor",
		"--summary", "Create the first editor screen",
	)
	var sendResp map[string]any
	mustDecodeJSON(t, sendOut, &sendResp)
	threadID := nestedString(t, sendResp, "data", "thread", "thread_id")

	var watchResult watchCommandResult
	select {
	case watchResult = <-watchCh:
	case <-time.After(3 * time.Second):
		t.Fatal("watch command did not return")
	}
	if watchResult.exit != 0 {
		t.Fatalf("watch failed with exit=%d\nstderr:\n%s\nstdout:\n%s", watchResult.exit, watchResult.stderr, watchResult.stdout)
	}

	var watchResp map[string]any
	mustDecodeJSON(t, watchResult.stdout, &watchResp)
	if woke, ok := nestedValue(t, watchResp, "data", "woke").(bool); !ok || !woke {
		t.Fatalf("expected watch to wake, got %#v", nestedValue(t, watchResp, "data", "woke"))
	}
	if got := nestedString(t, watchResp, "data", "thread", "thread_id"); got != threadID {
		t.Fatalf("expected woken thread %q, got %q", threadID, got)
	}
	nextEventID, ok := nestedValue(t, watchResp, "data", "next_event_id").(float64)
	if !ok {
		t.Fatalf("expected numeric next_event_id, got %#v", nestedValue(t, watchResp, "data", "next_event_id"))
	}
	eventID, ok := nestedValue(t, watchResp, "data", "event", "event_id").(float64)
	if !ok {
		t.Fatalf("expected numeric event_id, got %#v", nestedValue(t, watchResp, "data", "event", "event_id"))
	}
	if nextEventID != eventID {
		t.Fatalf("expected next_event_id == event.event_id, got %v vs %v", nextEventID, eventID)
	}
}

func TestWatchRespectsStatusFilter(t *testing.T) {
	t.Parallel()

	dbPath := filepath.Join(t.TempDir(), "coord.db")
	runInboxCommand(t, "--db", dbPath, "--json", "init")

	sendOut := runInboxCommand(
		t,
		"--db", dbPath,
		"--json",
		"send",
		"--from", "leader",
		"--to", "worker-c",
		"--subject", "Investigate policy edge case",
		"--summary", "Initial request",
	)
	var sendResp map[string]any
	mustDecodeJSON(t, sendOut, &sendResp)
	threadID := nestedString(t, sendResp, "data", "thread", "thread_id")

	watchCh := make(chan watchCommandResult, 1)
	go func() {
		stdout, stderr, exitCode := executeInboxCommand(
			"--db", dbPath,
			"--json",
			"watch",
			"--agent", "worker-c",
			"--status", "blocked",
			"--timeout-seconds", "2",
		)
		watchCh <- watchCommandResult{stdout: stdout, stderr: stderr, exit: exitCode}
	}()

	time.Sleep(200 * time.Millisecond)

	runInboxCommand(
		t,
		"--db", dbPath,
		"--json",
		"claim",
		"--agent", "worker-c",
		"--thread", threadID,
	)
	runInboxCommand(
		t,
		"--db", dbPath,
		"--json",
		"update",
		"--agent", "worker-c",
		"--thread", threadID,
		"--status", "blocked",
		"--summary", "Need policy decision",
	)

	var watchResult watchCommandResult
	select {
	case watchResult = <-watchCh:
	case <-time.After(3 * time.Second):
		t.Fatal("watch command did not return")
	}
	if watchResult.exit != 0 {
		t.Fatalf("watch failed with exit=%d\nstderr:\n%s\nstdout:\n%s", watchResult.exit, watchResult.stderr, watchResult.stdout)
	}

	var watchResp map[string]any
	mustDecodeJSON(t, watchResult.stdout, &watchResp)
	if status := nestedString(t, watchResp, "data", "thread", "status"); status != "blocked" {
		t.Fatalf("expected blocked status wake, got %q", status)
	}
}

func TestWatchTimesOutWithNoActivity(t *testing.T) {
	t.Parallel()

	dbPath := filepath.Join(t.TempDir(), "coord.db")
	runInboxCommand(t, "--db", dbPath, "--json", "init")

	stdout, _, exitCode := executeInboxCommand(
		"--db", dbPath,
		"--json",
		"watch",
		"--agent", "worker-d",
		"--status", "pending",
		"--timeout-seconds", "1",
	)
	if exitCode != 10 {
		t.Fatalf("expected watch timeout exit code 10, got %d with %s", exitCode, stdout)
	}
	assertErrorJSON(t, stdout, "no_matching_work")
}

