package inbox

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

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

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

	createThreadForList(t, dbPath, "leader", "worker-d", "Task pending", "Pending task")
	blockedThreadID := createThreadForList(t, dbPath, "leader", "worker-d", "Task blocked", "Blocked task")
	runInboxCommand(t, "--db", dbPath, "--json", "claim", "--agent", "worker-d", "--thread", blockedThreadID)
	runInboxCommand(
		t,
		"--db", dbPath,
		"--json",
		"update",
		"--agent", "worker-d",
		"--thread", blockedThreadID,
		"--status", "blocked",
		"--summary", "Need policy decision",
	)

	listOut := runInboxCommand(
		t,
		"--db", dbPath,
		"--json",
		"list",
		"--agent", "worker-d",
		"--status", "pending,blocked",
	)

	var listResp map[string]any
	mustDecodeJSON(t, listOut, &listResp)
	threads, ok := nestedValue(t, listResp, "data", "threads").([]any)
	if !ok || len(threads) < 2 {
		t.Fatalf("expected at least two matching threads, got %#v", nestedValue(t, listResp, "data", "threads"))
	}
	for _, raw := range threads {
		thread, ok := raw.(map[string]any)
		if !ok {
			t.Fatalf("expected thread object, got %#v", raw)
		}
		status, _ := thread["status"].(string)
		if status != "pending" && status != "blocked" {
			t.Fatalf("expected status pending or blocked, got %#v", status)
		}
	}
}

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

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

	createThreadForList(t, dbPath, "leader", "worker-d", "Leader task", "From leader")
	createThreadForList(t, dbPath, "planner", "worker-d", "Planner task", "From planner")

	listOut := runInboxCommand(
		t,
		"--db", dbPath,
		"--json",
		"list",
		"--created-by", "leader",
		"--assigned-to", "worker-d",
	)

	var listResp map[string]any
	mustDecodeJSON(t, listOut, &listResp)
	threads, ok := nestedValue(t, listResp, "data", "threads").([]any)
	if !ok || len(threads) == 0 {
		t.Fatalf("expected matching leader-created threads, got %#v", nestedValue(t, listResp, "data", "threads"))
	}
	for _, raw := range threads {
		thread, ok := raw.(map[string]any)
		if !ok {
			t.Fatalf("expected thread object, got %#v", raw)
		}
		if got := thread["created_by"]; got != "leader" {
			t.Fatalf("expected created_by leader, got %#v", got)
		}
	}
}

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

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

	createThreadForList(t, dbPath, "leader", "worker-d", "Worker D task", "For worker-d")
	createThreadForList(t, dbPath, "leader", "worker-e", "Worker E task", "For worker-e")

	listOut := runInboxCommand(
		t,
		"--db", dbPath,
		"--json",
		"list",
		"--assigned-to", "worker-d",
		"--status", "pending",
	)

	var listResp map[string]any
	mustDecodeJSON(t, listOut, &listResp)
	threads, ok := nestedValue(t, listResp, "data", "threads").([]any)
	if !ok || len(threads) == 0 {
		t.Fatalf("expected assigned-to match, got %#v", nestedValue(t, listResp, "data", "threads"))
	}
	for _, raw := range threads {
		thread, ok := raw.(map[string]any)
		if !ok {
			t.Fatalf("expected thread object, got %#v", raw)
		}
		if got := thread["assigned_to"]; got != "worker-d" {
			t.Fatalf("expected assigned_to worker-d, got %#v", got)
		}
		if got := thread["status"]; got != "pending" {
			t.Fatalf("expected pending status, got %#v", got)
		}
	}
}

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

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

	createThreadForList(t, dbPath, "leader", "worker-d", "Task 1", "Earlier task")
	time.Sleep(20 * time.Millisecond)
	createThreadForList(t, dbPath, "leader", "worker-d", "Task 2", "Latest task")

	listOut := runInboxCommand(
		t,
		"--db", dbPath,
		"--json",
		"list",
		"--assigned-to", "worker-d",
		"--limit", "1",
	)

	var listResp map[string]any
	mustDecodeJSON(t, listOut, &listResp)
	threads, ok := nestedValue(t, listResp, "data", "threads").([]any)
	if !ok {
		t.Fatalf("expected threads array, got %#v", nestedValue(t, listResp, "data", "threads"))
	}
	if len(threads) != 1 {
		t.Fatalf("expected exactly one row for limit=1, got %d", len(threads))
	}
	thread, ok := threads[0].(map[string]any)
	if !ok {
		t.Fatalf("expected thread object, got %#v", threads[0])
	}
	if got := thread["subject"]; got != "Task 2" {
		t.Fatalf("expected latest thread subject Task 2, got %#v", got)
	}
}

func createThreadForList(t *testing.T, dbPath, from, to, subject, summary string) string {
	t.Helper()

	sendOut := runInboxCommand(
		t,
		"--db", dbPath,
		"--json",
		"send",
		"--from", from,
		"--to", to,
		"--subject", subject,
		"--summary", summary,
	)
	var sendResp map[string]any
	mustDecodeJSON(t, sendOut, &sendResp)
	return nestedString(t, sendResp, "data", "thread", "thread_id")
}

