// ABOUTME: Tests for the chat protocol layer.
// ABOUTME: Uses httptest mock servers to test protocol functions.

package chat

import (
	"context"
	"crypto/ed25519"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/awebai/aw/awid"
)

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

	aliases, dids, addresses := classifyChatTargets([]string{"ops~alice"})
	if len(aliases) != 1 || aliases[0] != "ops~alice" {
		t.Fatalf("aliases=%v", aliases)
	}
	if len(dids) != 0 {
		t.Fatalf("dids=%v", dids)
	}
	if len(addresses) != 0 {
		t.Fatalf("addresses=%v", addresses)
	}
}

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

	aliases, dids, addresses := classifyChatTargets([]string{"@jane/c3po"})
	if len(addresses) != 1 || addresses[0] != "jane.aweb.ai/c3po" {
		t.Fatalf("addresses=%v", addresses)
	}
	if len(aliases) != 0 {
		t.Fatalf("aliases=%v", aliases)
	}
	if len(dids) != 0 {
		t.Fatalf("dids=%v", dids)
	}
}

// mockHandler dispatches requests to registered handlers by exact method+path match.
type mockHandler struct {
	handlers map[string]http.HandlerFunc
}

func (m *mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	key := r.Method + " " + r.URL.Path
	if h, ok := m.handlers[key]; ok {
		h(w, r)
		return
	}
	http.NotFound(w, r)
}

func newMockServer(handlers map[string]http.HandlerFunc) *httptest.Server {
	return httptest.NewServer(&mockHandler{handlers: handlers})
}

func mustClient(t *testing.T, url string) *awid.Client {
	t.Helper()
	c, err := awid.New(url)
	if err != nil {
		t.Fatal(err)
	}
	return c
}

func mustIdentityClient(t *testing.T, url string) *awid.Client {
	t.Helper()
	pub, priv, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	c, err := awid.NewWithIdentity(url, priv, awid.ComputeDIDKey(pub))
	if err != nil {
		t.Fatal(err)
	}
	return c
}

func mustTeamClient(t *testing.T, url string, teamID string) *awid.Client {
	t.Helper()
	pub, priv, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	_, teamPriv, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	cert, err := awid.SignTeamCertificate(teamPriv, awid.TeamCertificateFields{
		Team:         teamID,
		MemberDIDKey: awid.ComputeDIDKey(pub),
		Alias:        "alice",
		Lifetime:     awid.LifetimePersistent,
	})
	if err != nil {
		t.Fatal(err)
	}
	c, err := awid.NewWithCertificate(url, priv, cert)
	if err != nil {
		t.Fatal(err)
	}
	return c
}

type stubIdentityResolver struct {
	resolve func(context.Context, string) (*awid.ResolvedIdentity, error)
	verify  func(context.Context, string, string) *awid.StableIdentityVerification
}

func (r stubIdentityResolver) Resolve(ctx context.Context, identifier string) (*awid.ResolvedIdentity, error) {
	if r.resolve == nil {
		return nil, errors.New("no resolver configured")
	}
	return r.resolve(ctx, identifier)
}

func (r stubIdentityResolver) VerifyStableIdentity(ctx context.Context, address, stableID string) *awid.StableIdentityVerification {
	if r.verify == nil {
		return nil
	}
	return r.verify(ctx, address, stableID)
}

func jsonResponse(w http.ResponseWriter, v any) {
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(v)
}

func deliveredIDsTestPath(t *testing.T) string {
	t.Helper()
	tmp := t.TempDir()
	path := filepath.Join(tmp, ".aw", DeliveredIDsFileName)
	prev, hadPrev := os.LookupEnv(DeliveredIDsPathEnv)
	if err := os.Setenv(DeliveredIDsPathEnv, path); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		if hadPrev {
			_ = os.Setenv(DeliveredIDsPathEnv, prev)
			return
		}
		_ = os.Unsetenv(DeliveredIDsPathEnv)
	})
	return tmp
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}, LastMessage: "hi", LastFrom: "bob", UnreadCount: 1},
				},
				MessagesWaiting: 2,
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := Pending(context.Background(), mustClient(t, server.URL))
	if err != nil {
		t.Fatal(err)
	}
	if len(result.Pending) != 1 {
		t.Fatalf("pending=%d", len(result.Pending))
	}
	if result.Pending[0].SessionID != "s1" {
		t.Fatalf("session_id=%s", result.Pending[0].SessionID)
	}
	if result.MessagesWaiting != 2 {
		t.Fatalf("messages_waiting=%d", result.MessagesWaiting)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}, UnreadCount: 1},
				},
				MessagesWaiting: 1,
			})
		},
		// No /v1/network/chat/pending handler — returns 404.
	})
	t.Cleanup(server.Close)

	result, err := Pending(context.Background(), mustClient(t, server.URL))
	if err != nil {
		t.Fatal(err)
	}
	if len(result.Pending) != 1 {
		t.Fatalf("pending=%d, want 1 (local only, network gracefully skipped)", len(result.Pending))
	}
	if result.Pending[0].SessionID != "s1" {
		t.Fatalf("session_id=%s", result.Pending[0].SessionID)
	}
	if result.MessagesWaiting != 1 {
		t.Fatalf("messages_waiting=%d, want 1", result.MessagesWaiting)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:            "s1",
						Participants:         []string{"alice", "bob"},
						ParticipantAddresses: []string{"acme/alice", "otherco/bob"},
						LastMessage:          "hi",
						LastFrom:             "bob",
						LastFromAddress:      "",
						UnreadCount:          1,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := Pending(context.Background(), mustClient(t, server.URL))
	if err != nil {
		t.Fatal(err)
	}
	if got := result.Pending[0].LastFromAddress; got != "otherco/bob" {
		t.Fatalf("last_from_address=%q", got)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:       "s1",
						Participants:    []string{"alice", "bob"},
						ParticipantDIDs: []string{"did:aw:alice", "did:aw:bob"},
						LastMessage:     "hi",
						LastFrom:        "bob",
						LastFromDID:     "",
						UnreadCount:     1,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := Pending(context.Background(), mustClient(t, server.URL))
	if err != nil {
		t.Fatal(err)
	}
	if got := result.Pending[0].LastFromStableID; got != "did:aw:bob" {
		t.Fatalf("last_from_stable_id=%q", got)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}},
				},
			})
		},
		"POST /v1/chat/sessions/s1/messages": func(w http.ResponseWriter, r *http.Request) {
			var req awid.ChatSendMessageRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			if !req.ExtendWait {
				t.Error("expected extend_wait=true")
			}
			jsonResponse(w, awid.ChatSendMessageResponse{
				MessageID:          "msg-1",
				Delivered:          true,
				ExtendsWaitSeconds: 300,
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ExtendWait(context.Background(), mustClient(t, server.URL), "bob", "thinking...")
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%s", result.SessionID)
	}
	if result.ExtendsWaitSeconds != 300 {
		t.Fatalf("extends_wait_seconds=%d", result.ExtendsWaitSeconds)
	}
}

func TestOpen(t *testing.T) {
	t.Parallel()
	deliveredIDsTestPath(t)

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}, SenderWaiting: true},
				},
			})
		},
		"GET /v1/chat/sessions/s1/messages": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatHistoryResponse{
				Messages: []awid.ChatMessage{
					{MessageID: "m1", FromAgent: "bob", Body: "hello", Timestamp: "2025-01-01T00:00:00Z"},
					{MessageID: "m2", FromAgent: "bob", Body: "are you there?", Timestamp: "2025-01-01T00:00:01Z"},
				},
			})
		},
		"POST /v1/chat/sessions/s1/read": func(w http.ResponseWriter, r *http.Request) {
			var req awid.ChatMarkReadRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			if req.UpToMessageID != "m2" {
				t.Errorf("up_to_message_id=%s", req.UpToMessageID)
			}
			jsonResponse(w, awid.ChatMarkReadResponse{
				Success:        true,
				MessagesMarked: 2,
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := Open(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%s", result.SessionID)
	}
	if len(result.Messages) != 2 {
		t.Fatalf("messages=%d", len(result.Messages))
	}
	if result.MarkedRead != 2 {
		t.Fatalf("marked_read=%d", result.MarkedRead)
	}
	if !result.SenderWaiting {
		t.Fatal("sender_waiting=false")
	}
}

func TestOpenSupportsExactAddressTarget(t *testing.T) {
	deliveredIDsTestPath(t)

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:            "s1",
						Participants:         []string{"alice", "monitor"},
						ParticipantAddresses: []string{"", "otherco/monitor"},
						SenderWaiting:        true,
					},
				},
			})
		},
		"GET /v1/chat/sessions/s1/messages": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatHistoryResponse{
				Messages: []awid.ChatMessage{
					{MessageID: "m1", FromAgent: "monitor", FromAddress: "otherco/monitor", Body: "hello", Timestamp: "2025-01-01T00:00:00Z"},
				},
			})
		},
		"POST /v1/chat/sessions/s1/read": func(w http.ResponseWriter, r *http.Request) {
			var req awid.ChatMarkReadRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			jsonResponse(w, awid.ChatMarkReadResponse{
				Success:        true,
				MessagesMarked: 1,
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := Open(context.Background(), mustClient(t, server.URL), "otherco/monitor")
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%s", result.SessionID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "monitor"}, SenderWaiting: true},
				},
			})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{Sessions: []awid.ChatSessionItem{}})
		},
	})
	t.Cleanup(server.Close)

	_, err := Open(context.Background(), mustClient(t, server.URL), "otherco/monitor")
	if err == nil {
		t.Fatal("expected no conversation found")
	}
	if !strings.Contains(err.Error(), "no conversation found with otherco/monitor") {
		t.Fatalf("err=%v", err)
	}
}

func TestOpenSupportsStableDIDTargetViaResolvedAddress(t *testing.T) {
	deliveredIDsTestPath(t)

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:            "s1",
						Participants:         []string{"alice", "monitor"},
						ParticipantAddresses: []string{"", "otherco/monitor"},
						SenderWaiting:        true,
					},
				},
			})
		},
		"GET /v1/chat/sessions/s1/messages": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatHistoryResponse{
				Messages: []awid.ChatMessage{
					{MessageID: "m1", FromAgent: "monitor", FromAddress: "otherco/monitor", Body: "hello", Timestamp: "2025-01-01T00:00:00Z"},
				},
			})
		},
		"POST /v1/chat/sessions/s1/read": func(w http.ResponseWriter, r *http.Request) {
			var req awid.ChatMarkReadRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			jsonResponse(w, awid.ChatMarkReadResponse{
				Success:        true,
				MessagesMarked: 1,
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			if identifier != "did:aw:monitor" && identifier != "otherco/monitor" {
				t.Fatalf("identifier=%q", identifier)
			}
			return &awid.ResolvedIdentity{
				DID:         "did:key:z6MkMonitor",
				StableID:    "did:aw:monitor",
				Address:     "otherco/monitor",
				Handle:      "monitor",
				ResolvedVia: "registry",
			}, nil
		},
	})

	result, err := Open(context.Background(), client, "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%s", result.SessionID)
	}
}

func TestOpenRetriesMarkReadOnce(t *testing.T) {
	var markReadCalls int

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}},
				},
			})
		},
		"GET /v1/chat/sessions/s1/messages": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatHistoryResponse{
				Messages: []awid.ChatMessage{
					{MessageID: "retry-m1", FromAgent: "bob", Body: "hello", Timestamp: "2025-01-01T00:00:00Z"},
				},
			})
		},
		"POST /v1/chat/sessions/s1/read": func(w http.ResponseWriter, r *http.Request) {
			markReadCalls++
			if markReadCalls == 1 {
				http.Error(w, "try again", http.StatusInternalServerError)
				return
			}
			var req awid.ChatMarkReadRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			if req.UpToMessageID != "retry-m1" {
				t.Errorf("up_to_message_id=%s", req.UpToMessageID)
			}
			jsonResponse(w, awid.ChatMarkReadResponse{Success: true, MessagesMarked: 1})
		},
	})
	t.Cleanup(server.Close)

	result, err := Open(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if markReadCalls != 2 {
		t.Fatalf("mark_read_calls=%d, want 2", markReadCalls)
	}
	if result.MarkedRead != 1 {
		t.Fatalf("marked_read=%d", result.MarkedRead)
	}
}

func TestOpenCachesDeliveredIDsBeforeFailedMarkRead(t *testing.T) {
	tmp := deliveredIDsTestPath(t)

	var markReadCalls int

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}},
				},
			})
		},
		"GET /v1/chat/sessions/s1/messages": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatHistoryResponse{
				Messages: []awid.ChatMessage{
					{MessageID: "m1", FromAgent: "bob", Body: "hello", Timestamp: "2025-01-01T00:00:00Z"},
					{MessageID: "m2", FromAgent: "bob", Body: "again", Timestamp: "2025-01-01T00:00:01Z"},
				},
			})
		},
		"POST /v1/chat/sessions/s1/read": func(w http.ResponseWriter, _ *http.Request) {
			markReadCalls++
			http.Error(w, "still failing", http.StatusInternalServerError)
		},
	})
	t.Cleanup(server.Close)

	result, err := Open(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if markReadCalls != 2 {
		t.Fatalf("mark_read_calls=%d, want 2", markReadCalls)
	}
	if len(result.Messages) != 2 {
		t.Fatalf("messages=%d, want 2", len(result.Messages))
	}

	delivered, err := LoadDeliveredIDsForDir(tmp)
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := delivered["m1"]; !ok {
		t.Fatalf("missing delivered id m1: %#v", delivered)
	}
	if _, ok := delivered["m2"]; !ok {
		t.Fatalf("missing delivered id m2: %#v", delivered)
	}

	again, err := Open(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if len(again.Messages) != 0 {
		t.Fatalf("messages=%d, want 0 after cache filter", len(again.Messages))
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{SessionID: "s2", Participants: []string{"alice", "bob"}, CreatedAt: "2025-01-01T00:00:00Z"},
				},
			})
		},
		"GET /v1/chat/sessions/s2/messages": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatHistoryResponse{Messages: []awid.ChatMessage{}})
		},
	})
	t.Cleanup(server.Close)

	result, err := Open(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s2" {
		t.Fatalf("session_id=%s", result.SessionID)
	}
	if !result.UnreadWasEmpty {
		t.Fatal("expected unread_was_empty=true")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}},
				},
			})
		},
		"GET /v1/chat/sessions/s1/messages": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatHistoryResponse{
				Messages: []awid.ChatMessage{
					{MessageID: "m1", FromAgent: "alice", Body: "hello", Timestamp: "2025-01-01T00:00:00Z"},
					{MessageID: "m2", FromAgent: "bob", Body: "hi!", Timestamp: "2025-01-01T00:00:01Z"},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := History(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%s", result.SessionID)
	}
	if len(result.Messages) != 2 {
		t.Fatalf("messages=%d", len(result.Messages))
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}, LastMessage: "help!", LastFrom: "bob", SenderWaiting: true},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ShowPending(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "pending" {
		t.Fatalf("status=%s", result.Status)
	}
	if result.Reply != "help!" {
		t.Fatalf("reply=%s", result.Reply)
	}
	if !result.SenderWaiting {
		t.Fatal("sender_waiting=false")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:            "s1",
						Participants:         []string{"alice", "monitor"},
						ParticipantAddresses: []string{"", "otherco/monitor"},
						LastMessage:          "help!",
						LastFrom:             "monitor",
						SenderWaiting:        true,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ShowPending(context.Background(), mustClient(t, server.URL), "otherco/monitor")
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%s", result.SessionID)
	}
	if result.TargetAgent != "otherco/monitor" {
		t.Fatalf("target=%s", result.TargetAgent)
	}
}

func TestShowPendingSupportsStableDIDTargetViaResolvedAddress(t *testing.T) {
	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:            "s1",
						Participants:         []string{"alice", "monitor"},
						ParticipantAddresses: []string{"", "otherco/monitor"},
						LastMessage:          "help!",
						LastFrom:             "monitor",
						SenderWaiting:        true,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(&stubIdentityResolver{
		resolve: func(ctx context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			if identifier != "did:aw:monitor" {
				t.Fatalf("identifier=%q", identifier)
			}
			return &awid.ResolvedIdentity{
				DID:         "did:key:z6MkMonitor",
				StableID:    "did:aw:monitor",
				Address:     "otherco/monitor",
				Handle:      "monitor",
				RegistryURL: server.URL,
			}, nil
		},
	})

	result, err := ShowPending(context.Background(), client, "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%s", result.SessionID)
	}
	if result.TargetAgent != "did:aw:monitor" {
		t.Fatalf("target=%s", result.TargetAgent)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:       "s1",
						Participants:    []string{"alice", "monitor"},
						LastMessage:     "help!",
						LastFrom:        "monitor",
						LastFromAddress: "otherco/monitor",
						SenderWaiting:   true,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ShowPending(context.Background(), mustClient(t, server.URL), "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d", len(result.Events))
	}
	if result.Events[0].FromAddress != "otherco/monitor" {
		t.Fatalf("from_address=%q", result.Events[0].FromAddress)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:            "s1",
						Participants:         []string{""},
						ParticipantAddresses: []string{"otherco/monitor"},
						LastMessage:          "help!",
						LastFrom:             "",
						LastFromAddress:      "",
						SenderWaiting:        true,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ShowPending(context.Background(), mustClient(t, server.URL), "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d", len(result.Events))
	}
	if result.Events[0].FromAddress != "otherco/monitor" {
		t.Fatalf("from_address=%q", result.Events[0].FromAddress)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:            "s1",
						Participants:         []string{"alice", "monitor"},
						ParticipantAddresses: []string{"acme/alice", "otherco/monitor"},
						LastMessage:          "help!",
						LastFrom:             "monitor",
						LastFromAddress:      "",
						SenderWaiting:        true,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ShowPending(context.Background(), mustClient(t, server.URL), "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d", len(result.Events))
	}
	if result.Events[0].FromAddress != "otherco/monitor" {
		t.Fatalf("from_address=%q", result.Events[0].FromAddress)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:       "s1",
						Participants:    []string{"alice", "monitor"},
						ParticipantDIDs: []string{"did:aw:alice", "did:aw:monitor"},
						LastMessage:     "help!",
						LastFrom:        "monitor",
						LastFromDID:     "",
						SenderWaiting:   true,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ShowPending(context.Background(), mustClient(t, server.URL), "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d", len(result.Events))
	}
	if result.Events[0].FromStableID != "did:aw:monitor" {
		t.Fatalf("from_stable_id=%q", result.Events[0].FromStableID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:       "s1",
						Participants:    []string{""},
						ParticipantDIDs: []string{"did:aw:monitor"},
						LastMessage:     "help!",
						LastFrom:        "",
						LastFromDID:     "did:aw:monitor",
						SenderWaiting:   true,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ShowPending(context.Background(), mustClient(t, server.URL), "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d", len(result.Events))
	}
	if result.Events[0].FromStableID != "did:aw:monitor" {
		t.Fatalf("from_stable_id=%q", result.Events[0].FromStableID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:       "s1",
						Participants:    []string{""},
						ParticipantDIDs: []string{"did:aw:monitor"},
						LastMessage:     "help!",
						LastFrom:        "",
						LastFromDID:     "did:key:z6MkMonitorCurrent",
						SenderWaiting:   true,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ShowPending(context.Background(), mustClient(t, server.URL), "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d", len(result.Events))
	}
	if result.Events[0].FromStableID != "did:aw:monitor" {
		t.Fatalf("from_stable_id=%q", result.Events[0].FromStableID)
	}
	if result.Events[0].FromDID != "did:key:z6MkMonitorCurrent" {
		t.Fatalf("from_did=%q", result.Events[0].FromDID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:       "s1",
						Participants:    []string{"", ""},
						ParticipantDIDs: []string{"did:aw:dave", "did:aw:monitor"},
						LastMessage:     "help!",
						LastFrom:        "",
						LastFromDID:     "did:key:z6MkMonitorCurrent",
						SenderWaiting:   true,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ShowPending(context.Background(), mustClient(t, server.URL), "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d", len(result.Events))
	}
	if result.Events[0].FromStableID != "" {
		t.Fatalf("from_stable_id=%q, want empty on ambiguous participant stable ids", result.Events[0].FromStableID)
	}
	if result.Events[0].FromDID != "did:key:z6MkMonitorCurrent" {
		t.Fatalf("from_did=%q", result.Events[0].FromDID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:       "s1",
						Participants:    []string{""},
						ParticipantDIDs: []string{"did:aw:monitor"},
						LastMessage:     "help!",
						LastFrom:        "",
						LastFromDID:     "did:aw:monitor",
						SenderWaiting:   true,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ShowPending(context.Background(), mustClient(t, server.URL), "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%q", result.SessionID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:            "s1",
						Participants:         []string{""},
						ParticipantAddresses: []string{"otherco/monitor"},
						LastMessage:          "help!",
						LastFrom:             "",
						LastFromAddress:      "otherco/monitor",
						SenderWaiting:        true,
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := ShowPending(context.Background(), mustClient(t, server.URL), "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%q", result.SessionID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:       "s1",
						Participants:    []string{""},
						ParticipantDIDs: []string{"did:aw:monitor"},
						CreatedAt:       "2026-03-20T00:00:00Z",
					},
				},
			})
		},
		"GET /v1/chat/sessions/s1/messages": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatHistoryResponse{
				Messages: []awid.ChatMessage{
					{
						MessageID: "m1",
						FromDID:   "did:aw:monitor",
						Body:      "hello",
						Timestamp: "2026-03-20T00:00:01Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := History(context.Background(), mustClient(t, server.URL), "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%q", result.SessionID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, r *http.Request) {
			var req awid.ChatCreateSessionRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			if !req.Leaving {
				t.Error("expected leaving=true")
			}
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1", MessageID: "m1",
				SSEURL: "/v1/chat/sessions/s1/stream",
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "goodbye", SendOptions{Leaving: true, Wait: 60}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "sent" {
		t.Fatalf("status=%s", result.Status)
	}
}

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

	var gotBody awid.ChatSendMessageRequest
	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s1",
						Participants:         []string{"alice", "bob"},
						ParticipantAddresses: []string{"test.local/alice", "test.local/bob"},
						CreatedAt:            "2026-01-01T00:00:01Z",
					},
				},
			})
		},
		"POST /v1/chat/sessions/s1/messages": func(w http.ResponseWriter, r *http.Request) {
			if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
				t.Fatal(err)
			}
			jsonResponse(w, awid.ChatSendMessageResponse{MessageID: "m1", Delivered: true})
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"test.local/bob"}, "goodbye", SendOptions{Leaving: true}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%s, want s1", result.SessionID)
	}
	if !gotBody.Leaving {
		t.Fatal("leaving=false, want true")
	}
	if gotBody.Body != "goodbye" {
		t.Fatalf("body=%q, want goodbye", gotBody.Body)
	}
}

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

	var gotBody awid.ChatCreateSessionRequest
	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, r *http.Request) {
			if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
				t.Fatal(err)
			}
			jsonResponse(w, awid.ChatCreateSessionResponse{SessionID: "new-session", MessageID: "m1"})
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "goodbye", SendOptions{Leaving: true}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.SessionID != "new-session" {
		t.Fatalf("session_id=%s, want new-session", result.SessionID)
	}
	if len(gotBody.ToAliases) != 1 || gotBody.ToAliases[0] != "bob" {
		t.Fatalf("to_aliases=%v, want [bob]", gotBody.ToAliases)
	}
	if !gotBody.Leaving {
		t.Fatal("leaving=false, want true")
	}
}

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

	var gotBody awid.ChatSendMessageRequest
	var createSessionCalls int
	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s1",
						Participants:         []string{"alice", "bob"},
						ParticipantAddresses: []string{"test.local/alice", "test.local/bob"},
						CreatedAt:            "2026-01-01T00:00:01Z",
					},
				},
			})
		},
		"POST /v1/chat/sessions/s1/messages": func(w http.ResponseWriter, r *http.Request) {
			if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
				t.Fatal(err)
			}
			jsonResponse(w, awid.ChatSendMessageResponse{MessageID: "m1", Delivered: true})
		},
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			// Mirror server-side cross-team-private-address rejection so a regression
			// of the probe gate surfaces as a 404 from the create path.
			createSessionCalls++
			http.Error(w, `{"detail":"address not found"}`, http.StatusNotFound)
		},
	})
	t.Cleanup(server.Close)

	// Wait > 0 must not bypass the probe. Leaving:true keeps sendCommon from
	// blocking on SSE so the test stays focused on the gate at line 1115.
	result, err := Send(
		context.Background(),
		mustClient(t, server.URL),
		"alice",
		[]string{"test.local/bob"},
		"reply",
		SendOptions{Wait: 60, Leaving: true},
		nil,
	)
	if err != nil {
		t.Fatalf("Send returned error: %v", err)
	}
	if createSessionCalls != 0 {
		t.Fatalf("expected probe to find existing session and skip ChatCreateSession; create calls=%d", createSessionCalls)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%s, want s1", result.SessionID)
	}
	if gotBody.Body != "reply" {
		t.Fatalf("body=%q, want reply", gotBody.Body)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1", MessageID: "m1",
				SSEURL: "/v1/chat/sessions/s1/stream",
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "fire and forget", SendOptions{Wait: 0}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "sent" {
		t.Fatalf("status=%s", result.Status)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, r *http.Request) {
			var req awid.ChatCreateSessionRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			if len(req.ToAddresses) != 1 || req.ToAddresses[0] != "otherco/monitor" {
				t.Fatalf("to_addresses=%v", req.ToAddresses)
			}
			if len(req.ToAliases) != 0 {
				t.Fatalf("to_aliases=%v, want empty", req.ToAliases)
			}
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1", MessageID: "m1",
				SSEURL: "/v1/chat/sessions/s1/stream",
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"otherco/monitor"}, "hello", SendOptions{Wait: 0}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "sent" {
		t.Fatalf("status=%s", result.Status)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID:   "s1",
				MessageID:   "m1",
				SSEURL:      "/v1/chat/sessions/s1/stream",
				TargetsLeft: []string{"bob"},
			})
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 60}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "targets_left" {
		t.Fatalf("status=%s", result.Status)
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			// Replay: our sent message
			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			// Reply from bob
			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_agent": "bob", "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if result.Reply != "hi back!" {
		t.Fatalf("reply=%s", result.Reply)
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "implementer", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type":       "message",
				"message_id": "msg-reply-1",
				"from_agent": "architect",
				"body":       "hi back!",
				"from_did":   "did:key:z6MkSender",
				"is_contact": false,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetAddress("myteam/implementer")
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case "myteam/architect":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkSender",
					Address:     identifier,
					Lifetime:    awid.LifetimeEphemeral,
					Custody:     awid.CustodySelf,
					ResolvedVia: "registry",
				}, nil
			case "myteam/implementer":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkSelf",
					Address:     identifier,
					Lifetime:    awid.LifetimePersistent,
					Custody:     awid.CustodySelf,
					ResolvedVia: "registry",
				}, nil
			default:
				t.Fatalf("identifier=%q", identifier)
				return nil, errors.New("unexpected identifier")
			}
		},
	})

	result, err := Send(context.Background(), client, "implementer", []string{"architect"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if result.Reply != "hi back!" {
		t.Fatalf("reply=%s", result.Reply)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d, want 1", len(result.Events))
	}
	if result.Events[0].IsContact != nil {
		t.Fatalf("ephemeral SSE sender should suppress contact tag, got %v", *result.Events[0].IsContact)
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			// Replay our sent message, then hang (no reply)
			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			// Block until client disconnects
			<-time.After(10 * time.Second)
		},
	})
	t.Cleanup(server.Close)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Send(ctx, mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 1}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "timeout" {
		t.Fatalf("status=%s (expected timeout)", result.Status)
	}
	if result.WaitedSeconds < 1 {
		t.Fatalf("waited_seconds=%d", result.WaitedSeconds)
	}
}

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

	sentMsgID := "msg-sent-1"
	var callbackCalls []string

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			// Our sent message
			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			// Bob sends extend-wait
			hangOnData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-hangon", "from_agent": "bob",
				"body": "thinking...", "hang_on": true, "extends_wait_seconds": 300,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", hangOnData)
			if flusher != nil {
				flusher.Flush()
			}

			// Bob sends actual reply
			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply", "from_agent": "bob", "body": "here's my answer",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	callback := func(kind, msg string) {
		callbackCalls = append(callbackCalls, kind+": "+msg)
	}

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, callback)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if result.Reply != "here's my answer" {
		t.Fatalf("reply=%s", result.Reply)
	}

	// Verify callbacks were called for extend_wait and wait_extended
	foundExtendWait := false
	foundExtended := false
	for _, c := range callbackCalls {
		if strings.HasPrefix(c, "extend_wait:") {
			foundExtendWait = true
		}
		if strings.HasPrefix(c, "wait_extended:") {
			foundExtended = true
		}
	}
	if !foundExtendWait {
		t.Fatal("missing extend_wait callback")
	}
	if !foundExtended {
		t.Fatal("missing wait_extended callback")
	}
}

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

	sentMsgID := "msg-sent-1"
	senderDID := "did:key:z6MkBobCurrent"
	stableID := "did:aw:bob"
	var callbackCalls []string

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:key:z6MkAliceCurrent"},
					{DID: senderDID},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			hangOnData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-hangon", "from_did": senderDID, "from_stable_id": stableID,
				"body": "thinking...", "hang_on": true, "extends_wait_seconds": 300,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", hangOnData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	callback := func(kind, msg string) {
		callbackCalls = append(callbackCalls, kind+": "+msg)
	}

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{stableID}, "hello", SendOptions{Wait: 1}, callback)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "timeout" {
		t.Fatalf("status=%s, want timeout", result.Status)
	}

	foundStable := false
	for _, c := range callbackCalls {
		if c == "extend_wait: did:aw:bob: thinking..." {
			foundStable = true
		}
		if strings.Contains(c, senderDID) {
			t.Fatalf("callback should not use participant did:key label: %v", callbackCalls)
		}
	}
	if !foundStable {
		t.Fatalf("missing stable-id callback, got %v", callbackCalls)
	}
}

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

	sentMsgID := "msg-sent-1"
	var callbackCalls []string

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			hangOnData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-hangon", "from_agent": "bob", "from_address": "otherco/bob",
				"body": "thinking...", "hang_on": true, "extends_wait_seconds": 300,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", hangOnData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply", "from_agent": "bob", "from_address": "otherco/bob", "body": "here's my answer",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	callback := func(kind, msg string) {
		callbackCalls = append(callbackCalls, kind+": "+msg)
	}

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"otherco/bob"}, "hello", SendOptions{Wait: 5}, callback)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}

	foundExtendWait := false
	foundExtended := false
	for _, c := range callbackCalls {
		if c == "extend_wait: otherco/bob: thinking..." {
			foundExtendWait = true
		}
		if c == "wait_extended: wait extended by 5 min (otherco/bob requested more time)" {
			foundExtended = true
		}
	}
	if !foundExtendWait || !foundExtended {
		t.Fatalf("callbackCalls=%v", callbackCalls)
	}
}

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

	sentMsgID := "msg-sent-1"
	targetDID := "did:aw:bob"
	var callbackCalls []string

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:aw:alice", Address: "acme.com/alice"},
					{Alias: "bob", DID: targetDID, Address: "otherco/bob"},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			hangOnData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-hangon", "from_stable_id": targetDID,
				"body": "thinking...", "hang_on": true, "extends_wait_seconds": 300,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", hangOnData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply", "from_stable_id": targetDID, "body": "here's my answer",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	callback := func(kind, msg string) {
		callbackCalls = append(callbackCalls, kind+": "+msg)
	}

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"otherco/bob"}, "hello", SendOptions{Wait: 5}, callback)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}

	foundExtendWait := false
	foundExtended := false
	for _, c := range callbackCalls {
		if c == "extend_wait: otherco/bob: thinking..." {
			foundExtendWait = true
		}
		if c == "wait_extended: wait extended by 5 min (otherco/bob requested more time)" {
			foundExtended = true
		}
	}
	if !foundExtendWait || !foundExtended {
		t.Fatalf("callbackCalls=%v", callbackCalls)
	}
}

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

	sentMsgID := "msg-sent-1"
	var callbackKinds []string

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			// Our sent message
			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			// Read receipt from bob with 300s extension (matches server behavior)
			rrData, _ := json.Marshal(map[string]any{
				"type": "read_receipt", "reader_alias": "bob", "extends_wait_seconds": 300,
			})
			fmt.Fprintf(w, "event: read_receipt\ndata: %s\n\n", rrData)
			if flusher != nil {
				flusher.Flush()
			}

			// Reply from bob
			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply", "from_agent": "bob", "body": "got it",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	callback := func(kind, _ string) {
		callbackKinds = append(callbackKinds, kind)
	}

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, callback)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}

	foundReadReceipt := false
	foundWaitExtended := false
	for _, k := range callbackKinds {
		if k == "read_receipt" {
			foundReadReceipt = true
		}
		if k == "wait_extended" {
			foundWaitExtended = true
		}
	}
	if !foundReadReceipt {
		t.Fatal("missing read_receipt callback")
	}
	if !foundWaitExtended {
		t.Fatal("missing wait_extended callback from read receipt")
	}
}

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

	sentMsgID := "msg-sent-1"
	var capturedDeadline time.Time

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, r *http.Request) {
			// Capture the deadline query parameter sent to the server.
			deadlineStr := r.URL.Query().Get("deadline")
			if deadlineStr != "" {
				capturedDeadline, _ = time.Parse(time.RFC3339Nano, deadlineStr)
			}

			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			// Send our message then reply immediately.
			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply", "from_agent": "bob", "body": "hi",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	before := time.Now()
	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}

	// The stream deadline should be at least maxStreamDeadline from now,
	// not just the wait timeout (5s).
	if capturedDeadline.IsZero() {
		t.Fatal("server did not receive deadline parameter")
	}
	minExpected := before.Add(maxStreamDeadline - 1*time.Second)
	if capturedDeadline.Before(minExpected) {
		t.Fatalf("stream deadline %v is too close to now; expected at least %v from request time", capturedDeadline, maxStreamDeadline)
	}
}

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

	sentMsgID := "msg-sent-1"
	targetStableID := "did:aw:bob"
	targetCurrentDID := "did:key:z6MkBobCurrent"
	var callbackCalls []string

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:key:z6MkAliceCurrent"},
					{DID: targetCurrentDID},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			rrData, _ := json.Marshal(map[string]any{
				"type": "read_receipt", "reader_alias": "", "extends_wait_seconds": 300,
			})
			fmt.Fprintf(w, "event: read_receipt\ndata: %s\n\n", rrData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply", "from_did": targetCurrentDID, "from_stable_id": targetStableID, "body": "got it",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case targetStableID, targetCurrentDID:
				return &awid.ResolvedIdentity{DID: targetCurrentDID, StableID: targetStableID}, nil
			default:
				return nil, errors.New("unexpected identifier")
			}
		},
	})

	callback := func(kind, msg string) {
		callbackCalls = append(callbackCalls, kind+": "+msg)
	}

	result, err := Send(context.Background(), client, "alice", []string{targetStableID}, "hello", SendOptions{Wait: 5}, callback)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}

	foundReceipt := false
	foundExtended := false
	for _, c := range callbackCalls {
		if c == "read_receipt: did:aw:bob opened the conversation" {
			foundReceipt = true
		}
		if c == "wait_extended: wait extended by 5 min (did:aw:bob opened the conversation)" {
			foundExtended = true
		}
	}
	if !foundReceipt || !foundExtended {
		t.Fatalf("callbacks=%v", callbackCalls)
	}
}

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

	client, err := awid.New("http://example.invalid")
	if err != nil {
		t.Fatal(err)
	}
	client.SetAddress("acme.com/rose")

	label := inferReadReceiptLabel(
		context.Background(),
		client,
		"rose",
		"",
		[]awid.ChatParticipant{
			{Alias: "rose", Address: "acme.com/rose", DID: "did:aw:self-rose"},
			{Alias: "rose", Address: "otherco/rose", DID: "did:aw:other-rose"},
		},
	)

	if label != "otherco/rose" {
		t.Fatalf("label=%q, want otherco/rose", label)
	}
}

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

	trust := chatEventTrustAddress(
		Event{
			FromAgent:    "rose",
			FromDID:      "did:aw:other-rose",
			FromStableID: "did:aw:other-rose",
		},
		[]awid.ChatParticipant{
			{Alias: "rose", Address: "acme.com/rose", DID: "did:aw:self-rose"},
			{Alias: "rose", Address: "otherco/rose", DID: "did:aw:other-rose"},
		},
	)

	if trust != "otherco/rose" {
		t.Fatalf("trust=%q, want otherco/rose", trust)
	}
}

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

	label := chatEventSenderLabel(
		Event{
			FromAgent:    "rose",
			FromDID:      "did:aw:other-rose",
			FromStableID: "did:aw:other-rose",
		},
		[]awid.ChatParticipant{
			{Alias: "rose", Address: "acme.com/rose", DID: "did:aw:self-rose"},
			{Alias: "rose", Address: "otherco/rose", DID: "did:aw:other-rose"},
		},
	)

	if label != "otherco/rose" {
		t.Fatalf("label=%q, want otherco/rose", label)
	}
}

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

	label := chatEventSenderLabel(
		Event{
			FromAgent:    "monitor",
			FromStableID: "did:aw:monitor",
		},
		[]awid.ChatParticipant{
			{Alias: "monitor", Address: "otherco.com/monitor", DID: "did:aw:other-monitor"},
			{Alias: "monitor", Address: "acme.com/monitor", DID: "did:aw:monitor"},
		},
	)

	if label != "acme.com/monitor" {
		t.Fatalf("label=%q, want acme.com/monitor", label)
	}
}

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

	label := chatEventSenderLabel(
		Event{
			FromAgent:    "monitor",
			FromStableID: "did:aw:monitor",
		},
		[]awid.ChatParticipant{
			{Alias: "monitor", DID: "did:aw:monitor"},
		},
	)

	if label != "did:aw:monitor" {
		t.Fatalf("label=%q, want did:aw:monitor", label)
	}
}

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

	trust := chatEventTrustAddress(
		Event{
			FromAgent:    "monitor",
			FromStableID: "did:aw:monitor",
		},
		[]awid.ChatParticipant{
			{Alias: "monitor", Address: "otherco.com/monitor", DID: "did:aw:other-monitor"},
			{Alias: "monitor", Address: "acme.com/monitor", DID: "did:aw:monitor"},
		},
	)

	if trust != "acme.com/monitor" {
		t.Fatalf("trust=%q, want acme.com/monitor", trust)
	}
}

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

	names := normalizedChatEventNames(
		Event{
			FromAgent:    "rose",
			FromDID:      "did:aw:self-rose",
			FromStableID: "did:aw:self-rose",
		},
		[]awid.ChatParticipant{
			{Alias: "rose", Address: "acme.com/rose", DID: "did:aw:self-rose"},
			{Alias: "rose", Address: "otherco/rose", DID: "did:aw:other-rose"},
		},
	)

	for _, name := range names {
		if name == "otherco/rose" || name == "did:aw:other-rose" {
			t.Fatalf("names=%v should not include alias-collision participant", names)
		}
	}
}

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

	names := normalizedChatEventNames(
		Event{
			FromAgent:    "monitor",
			FromStableID: "did:aw:monitor",
		},
		[]awid.ChatParticipant{
			{Alias: "monitor", Address: "acme.com/monitor", DID: "did:aw:monitor"},
			{Alias: "monitor", Address: "otherco.com/monitor", DID: "did:aw:other-monitor"},
		},
	)

	for _, name := range names {
		if name == "otherco.com/monitor" || name == "did:aw:other-monitor" {
			t.Fatalf("names=%v should not include alias-collision participant", names)
		}
	}
}

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

	names := normalizedChatEventNames(
		Event{
			FromAgent:    "bob",
			FromStableID: "did:aw:mallory",
		},
		[]awid.ChatParticipant{
			{Alias: "bob", Address: "otherco.com/bob", DID: "did:aw:bob"},
		},
	)

	for _, name := range names {
		if name == "bob" {
			t.Fatalf("names=%v should not include alias fallback for conflicting strong identity", names)
		}
	}
}

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

	if DefaultWait != 120 {
		t.Fatalf("DefaultWait=%d, want 120", DefaultWait)
	}
}

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

	if MaxSendTimeout != 16*time.Minute {
		t.Fatalf("MaxSendTimeout=%v, want 16m", MaxSendTimeout)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{SessionID: "s-fallback", Participants: []string{"alice", "bob"}},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, senderWaiting, err := findSession(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-fallback" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-fallback",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"otherco/monitor"},
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, senderWaiting, err := findSession(context.Background(), mustClient(t, server.URL), "otherco/monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-fallback" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{SessionID: "s-1", Participants: []string{"monitor"}},
					{SessionID: "s-2", Participants: []string{"monitor"}},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			if identifier != "did:aw:monitor" {
				t.Fatalf("identifier=%q", identifier)
			}
			return &awid.ResolvedIdentity{
				DID:         "did:key:z6MkMonitor",
				StableID:    "did:aw:monitor",
				Handle:      "monitor",
				ResolvedVia: "registry",
			}, nil
		},
	})

	_, _, err := findSession(context.Background(), client, "did:aw:monitor")
	if err == nil {
		t.Fatal("expected ambiguity error")
	}
	if !strings.Contains(err.Error(), "multiple conversations match monitor") {
		t.Fatalf("err=%v", err)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-rich",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
						CreatedAt:            "2025-01-01T00:00:01Z",
					},
					{
						SessionID:    "s-sparse",
						Participants: []string{"monitor"},
						CreatedAt:    "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			if identifier != "did:aw:monitor" {
				t.Fatalf("identifier=%q", identifier)
			}
			return &awid.ResolvedIdentity{
				DID:         "did:key:z6MkMonitor",
				StableID:    "did:aw:monitor",
				Handle:      "monitor",
				ResolvedVia: "registry",
			}, nil
		},
	})

	sessionID, senderWaiting, err := findSession(context.Background(), client, "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-rich" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:            "s-rich",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
						LastActivity:         "2025-01-01T00:00:01Z",
					},
					{
						SessionID:    "s-sparse",
						Participants: []string{"monitor"},
						LastActivity: "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			if identifier != "did:aw:monitor" {
				t.Fatalf("identifier=%q", identifier)
			}
			return &awid.ResolvedIdentity{
				DID:         "did:key:z6MkMonitor",
				StableID:    "did:aw:monitor",
				Handle:      "monitor",
				ResolvedVia: "registry",
			}, nil
		},
	})

	sessionID, senderWaiting, err := findSession(context.Background(), client, "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-rich" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from pending)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-rich",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
						ParticipantDIDs:      []string{"did:aw:monitor"},
						CreatedAt:            "2025-01-01T00:00:01Z",
					},
					{
						SessionID:       "s-did-only",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:aw:monitor"},
						CreatedAt:       "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, senderWaiting, err := findSession(context.Background(), mustClient(t, server.URL), "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-rich" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-rich",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
						ParticipantDIDs:      []string{"did:aw:monitor"},
						CreatedAt:            "2025-01-01T00:00:01Z",
					},
					{
						SessionID:       "s-did-only",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:aw:monitor"},
						CreatedAt:       "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			if identifier != "did:aw:monitor" {
				t.Fatalf("identifier=%q", identifier)
			}
			return &awid.ResolvedIdentity{
				DID:         "did:key:z6MkMonitor",
				StableID:    "did:aw:monitor",
				Handle:      "monitor",
				ResolvedVia: "registry",
			}, nil
		},
	})

	sessionID, senderWaiting, err := findSession(context.Background(), client, "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-rich" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-rich",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
						ParticipantDIDs:      []string{"did:key:z6MkMonitor"},
						CreatedAt:            "2025-01-01T00:00:01Z",
					},
					{
						SessionID:       "s-did-only",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:key:z6MkMonitor"},
						CreatedAt:       "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, senderWaiting, err := findSession(context.Background(), mustClient(t, server.URL), "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-rich" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-rich",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
						ParticipantDIDs:      []string{"did:key:z6MkMonitor"},
						CreatedAt:            "2025-01-01T00:00:01Z",
					},
					{
						SessionID:       "s-did-only",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:key:z6MkMonitor"},
						CreatedAt:       "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			if identifier != "did:aw:monitor" {
				t.Fatalf("identifier=%q", identifier)
			}
			return &awid.ResolvedIdentity{
				DID:         "did:key:z6MkMonitor",
				StableID:    "did:aw:monitor",
				Handle:      "monitor",
				ResolvedVia: "registry",
			}, nil
		},
	})

	sessionID, senderWaiting, err := findSession(context.Background(), client, "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-rich" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:       "s-stable",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:aw:monitor"},
						CreatedAt:       "2025-01-01T00:00:01Z",
					},
					{
						SessionID:       "s-current",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:key:z6MkMonitor"},
						CreatedAt:       "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case "did:aw:monitor":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkMonitor",
					StableID:    "did:aw:monitor",
					Handle:      "monitor",
					ResolvedVia: "registry",
				}, nil
			case "did:key:z6MkMonitor":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkMonitor",
					StableID:    "did:aw:monitor",
					Handle:      "monitor",
					ResolvedVia: "registry",
				}, nil
			default:
				t.Fatalf("identifier=%q", identifier)
				return nil, nil
			}
		},
	})

	sessionID, senderWaiting, err := findSession(context.Background(), client, "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-stable" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:       "s-stable",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:aw:monitor"},
						CreatedAt:       "2025-01-01T00:00:01Z",
					},
					{
						SessionID:       "s-current",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:key:z6MkMonitor"},
						CreatedAt:       "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case "did:aw:monitor":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkMonitor",
					StableID:    "did:aw:monitor",
					Handle:      "monitor",
					ResolvedVia: "registry",
				}, nil
			case "did:key:z6MkMonitor":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkMonitor",
					StableID:    "did:aw:monitor",
					Handle:      "monitor",
					ResolvedVia: "registry",
				}, nil
			default:
				t.Fatalf("identifier=%q", identifier)
				return nil, nil
			}
		},
	})

	sessionID, senderWaiting, err := findSession(context.Background(), client, "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-stable" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-address",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
						CreatedAt:            "2025-01-01T00:00:01Z",
					},
					{
						SessionID:       "s-stable",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:aw:monitor"},
						CreatedAt:       "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case "did:aw:monitor", "acme.com/monitor":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkMonitor",
					StableID:    "did:aw:monitor",
					Handle:      "monitor",
					Address:     "acme.com/monitor",
					ResolvedVia: "registry",
				}, nil
			default:
				t.Fatalf("identifier=%q", identifier)
				return nil, nil
			}
		},
	})

	sessionID, senderWaiting, err := findSession(context.Background(), client, "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-address" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-address",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
						CreatedAt:            "2025-01-01T00:00:01Z",
					},
					{
						SessionID:       "s-stable",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:aw:monitor"},
						CreatedAt:       "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case "did:aw:monitor", "acme.com/monitor":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkMonitor",
					StableID:    "did:aw:monitor",
					Handle:      "monitor",
					Address:     "acme.com/monitor",
					ResolvedVia: "registry",
				}, nil
			default:
				t.Fatalf("identifier=%q", identifier)
				return nil, nil
			}
		},
	})

	sessionID, senderWaiting, err := findSession(context.Background(), client, "did:aw:monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-address" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-address",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
						ParticipantDIDs:      []string{"did:aw:monitor"},
						CreatedAt:            "2025-01-01T00:00:00Z",
					},
					{
						SessionID:       "s-did-only",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:aw:monitor"},
						CreatedAt:       "2025-01-01T00:00:01Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, senderWaiting, err := findSession(context.Background(), mustClient(t, server.URL), "acme.com/monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-address" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-address",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
						CreatedAt:            "2025-01-01T00:00:01Z",
					},
					{
						SessionID:       "s-current",
						Participants:    []string{"monitor"},
						ParticipantDIDs: []string{"did:key:z6MkMonitor"},
						CreatedAt:       "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case "did:key:z6MkMonitor", "acme.com/monitor":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkMonitor",
					StableID:    "did:aw:monitor",
					Handle:      "monitor",
					Address:     "acme.com/monitor",
					ResolvedVia: "registry",
				}, nil
			default:
				t.Fatalf("identifier=%q", identifier)
				return nil, nil
			}
		},
	})

	sessionID, senderWaiting, err := findSession(context.Background(), client, "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-address" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-1",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
					},
					{
						SessionID:            "s-2",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"otherco.com/monitor"},
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	_, _, err := findSession(context.Background(), mustClient(t, server.URL), "monitor")
	if err == nil {
		t.Fatal("expected ambiguity error")
	}
	if !strings.Contains(err.Error(), "multiple conversations match monitor") {
		t.Fatalf("err=%v", err)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-rich",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"acme.com/monitor"},
						CreatedAt:            "2025-01-01T00:00:01Z",
					},
					{
						SessionID:    "s-sparse",
						Participants: []string{"monitor"},
						CreatedAt:    "2025-01-01T00:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, senderWaiting, err := findSession(context.Background(), mustClient(t, server.URL), "monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-rich" {
		t.Fatalf("session_id=%s", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true (expected false from fallback)")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{
						SessionID:            "s-direct",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"otherco/monitor"},
						LastActivity:         "2026-04-11T10:00:00Z",
					},
					{
						SessionID:            "s-group-sparse",
						Participants:         nil,
						ParticipantAddresses: []string{"otherco/monitor", "acme/alice"},
						LastActivity:         "2026-04-11T09:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, senderWaiting, err := findSession(context.Background(), mustClient(t, server.URL), "otherco/monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-direct" {
		t.Fatalf("session_id=%q, want s-direct", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true, want false")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "s-direct",
						Participants:         []string{"monitor"},
						ParticipantAddresses: []string{"otherco/monitor"},
						CreatedAt:            "2026-04-11T10:00:00Z",
					},
					{
						SessionID:            "s-group-sparse",
						Participants:         nil,
						ParticipantAddresses: []string{"otherco/monitor", "acme/alice"},
						CreatedAt:            "2026-04-11T09:00:00Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, senderWaiting, err := findSession(context.Background(), mustClient(t, server.URL), "otherco/monitor")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-direct" {
		t.Fatalf("session_id=%q, want s-direct", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true, want false")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{Sessions: []awid.ChatSessionItem{}})
		},
	})
	t.Cleanup(server.Close)

	_, _, err := findSession(context.Background(), mustClient(t, server.URL), "nobody")
	if err == nil {
		t.Fatal("expected error for missing session")
	}
	if !strings.Contains(err.Error(), "no conversation found") {
		t.Fatalf("err=%s", err)
	}
}

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

	tests := []struct {
		name  string
		event awid.SSEEvent
		check func(t *testing.T, ev Event)
	}{
		{
			name: "message event",
			event: awid.SSEEvent{
				Event: "message",
				Data:  `{"session_id":"s1","message_id":"m1","from_agent":"bob","body":"hello","sender_leaving":false,"hang_on":false,"extends_wait_seconds":0}`,
			},
			check: func(t *testing.T, ev Event) {
				if ev.Type != "message" {
					t.Fatalf("type=%s", ev.Type)
				}
				if ev.FromAgent != "bob" {
					t.Fatalf("from_agent=%s", ev.FromAgent)
				}
				if ev.Body != "hello" {
					t.Fatalf("body=%s", ev.Body)
				}
			},
		},
		{
			name: "read receipt event",
			event: awid.SSEEvent{
				Event: "read_receipt",
				Data:  `{"type":"read_receipt","reader_alias":"bob","extends_wait_seconds":60}`,
			},
			check: func(t *testing.T, ev Event) {
				if ev.Type != "read_receipt" {
					t.Fatalf("type=%s", ev.Type)
				}
				if ev.ReaderAlias != "bob" {
					t.Fatalf("reader_alias=%s", ev.ReaderAlias)
				}
				if ev.ExtendsWaitSeconds != 60 {
					t.Fatalf("extends_wait_seconds=%d", ev.ExtendsWaitSeconds)
				}
			},
		},
		{
			name: "extend wait event",
			event: awid.SSEEvent{
				Event: "message",
				Data:  `{"type":"message","from_agent":"bob","body":"thinking...","hang_on":true,"extends_wait_seconds":300}`,
			},
			check: func(t *testing.T, ev Event) {
				if !ev.ExtendWait {
					t.Fatal("extend_wait=false")
				}
				if ev.ExtendsWaitSeconds != 300 {
					t.Fatalf("extends_wait_seconds=%d", ev.ExtendsWaitSeconds)
				}
			},
		},
		{
			name: "from fallback",
			event: awid.SSEEvent{
				Event: "message",
				Data:  `{"from":"bob","body":"legacy"}`,
			},
			check: func(t *testing.T, ev Event) {
				if ev.FromAgent != "bob" {
					t.Fatalf("from_agent=%s (expected fallback from 'from')", ev.FromAgent)
				}
			},
		},
		{
			name: "invalid JSON",
			event: awid.SSEEvent{
				Event: "message",
				Data:  "not json",
			},
			check: func(t *testing.T, ev Event) {
				if ev.Type != "message" {
					t.Fatalf("type=%s (should preserve event type)", ev.Type)
				}
				if ev.FromAgent != "" {
					t.Fatalf("from_agent=%s (should be empty on parse error)", ev.FromAgent)
				}
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ev := parseSSEEvent(&tt.event)
			tt.check(t, ev)
		})
	}
}

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

	pr, pw := io.Pipe()
	stream := awid.NewSSEStream(pr)
	defer pw.Close()

	ctx, cancel := context.WithCancel(context.Background())
	events, cleanup := streamToChannel(ctx, stream)
	_ = events

	cancel()

	// cleanup must return (not hang), proving the goroutine exited.
	done := make(chan struct{})
	go func() {
		cleanup()
		close(done)
	}()

	select {
	case <-done:
		// OK
	case <-time.After(2 * time.Second):
		t.Fatal("cleanup did not return — goroutine leaked")
	}
}

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

	pr, pw := io.Pipe()
	stream := awid.NewSSEStream(pr)

	ctx, cancel := context.WithCancel(context.Background())
	events, cleanup := streamToChannel(ctx, stream)
	_ = events

	// Don't write anything — goroutine is blocked inside stream.Next().
	time.Sleep(50 * time.Millisecond)

	cancel()

	done := make(chan struct{})
	go func() {
		cleanup()
		close(done)
	}()

	select {
	case <-done:
		// OK — goroutine unblocked from stream.Next() and exited.
	case <-time.After(2 * time.Second):
		t.Fatal("cleanup did not return — goroutine stuck in stream.Next()")
	}

	pw.Close()
}

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

	pr, pw := io.Pipe()
	stream := awid.NewSSEStream(pr)

	// Write enough events to fill the channel buffer (capacity 10).
	go func() {
		for i := 0; i < 15; i++ {
			fmt.Fprintf(pw, "event: message\ndata: {\"i\":%d}\n\n", i)
		}
	}()

	ctx, cancel := context.WithCancel(context.Background())
	events, cleanup := streamToChannel(ctx, stream)
	_ = events // deliberately don't read

	// Give goroutine time to fill the buffer.
	time.Sleep(100 * time.Millisecond)

	cancel()

	done := make(chan struct{})
	go func() {
		cleanup()
		close(done)
	}()

	select {
	case <-done:
		// OK — goroutine cleaned up even with full channel buffer.
	case <-time.After(2 * time.Second):
		t.Fatal("cleanup did not return — goroutine leaked with full channel buffer")
	}

	pw.Close()
}

// --- Fix 1: WaitExplicit prevents sentinel upgrade ---

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			// Reply immediately so we can check the timer was set to DefaultWait, not 300.
			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply", "from_agent": "bob", "body": "hi",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	// StartConversation=true, Wait=DefaultWait, WaitExplicit=true
	// Should wait DefaultWait seconds, NOT 300s.
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Send(ctx, mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{
		Wait:              DefaultWait,
		WaitExplicit:      true,
		StartConversation: true,
	}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			<-time.After(10 * time.Second)
		},
	})
	t.Cleanup(server.Close)

	// Wait=1 with WaitExplicit=false + StartConversation=true should upgrade to 300s.
	// Without the upgrade the wait timer would fire at 1s and return "sent".
	// With the upgrade the wait is 300s, so the 2s context expires first.
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	_, err := Send(ctx, mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{
		Wait:              1,
		WaitExplicit:      false,
		StartConversation: true,
	}, nil)
	if err != context.DeadlineExceeded {
		t.Fatalf("expected context.DeadlineExceeded (proving wait was upgraded past 1s), got %v", err)
	}
}

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

	var gotBody awid.ChatCreateSessionRequest
	var listedSessions bool
	var createSessionCalls int

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			listedSessions = true
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:    "s-existing",
						Participants: []string{"alice", "bob"},
						CreatedAt:    "2026-01-01T00:00:01Z",
						LastActivity: "2026-01-01T00:00:05Z",
					},
				},
			})
		},
		"POST /v1/chat/sessions": func(w http.ResponseWriter, r *http.Request) {
			createSessionCalls++
			if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
				t.Fatal(err)
			}
			jsonResponse(w, awid.ChatCreateSessionResponse{SessionID: "s-new", MessageID: "m-new"})
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{
		Wait:              0,
		WaitExplicit:      true,
		StartConversation: true,
	}, nil)
	if err != nil {
		t.Fatalf("Send returned error: %v", err)
	}
	if listedSessions {
		t.Fatal("start-conversation must not probe existing sessions")
	}
	if createSessionCalls != 1 {
		t.Fatalf("create calls=%d, want 1", createSessionCalls)
	}
	if result.SessionID != "s-new" {
		t.Fatalf("session_id=%s, want s-new", result.SessionID)
	}
	if gotBody.Message != "hello" {
		t.Fatalf("message=%q, want hello", gotBody.Message)
	}
}

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

	var gotBody awid.ChatCreateSessionRequest
	var listedSessions bool
	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			listedSessions = true
			jsonResponse(w, awid.ChatListSessionsResponse{Sessions: []awid.ChatSessionItem{}})
		},
		"POST /v1/chat/sessions": func(w http.ResponseWriter, r *http.Request) {
			if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
				t.Fatal(err)
			}
			jsonResponse(w, awid.ChatCreateSessionResponse{SessionID: "s-new", MessageID: "m-new"})
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "bye", SendOptions{
		Leaving:           true,
		StartConversation: true,
	}, nil)
	if err != nil {
		t.Fatalf("Send returned error: %v", err)
	}
	if listedSessions {
		t.Fatal("start-conversation send-and-leave must not probe existing sessions")
	}
	if result.SessionID != "s-new" {
		t.Fatalf("session_id=%s, want s-new", result.SessionID)
	}
	if !gotBody.Leaving {
		t.Fatal("leaving=false, want true")
	}
	if gotBody.WaitSeconds != nil {
		t.Fatalf("wait_seconds=%d, want omitted for send-and-leave", *gotBody.WaitSeconds)
	}
}

// --- Fix 2: Listen() ---

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}, SenderWaiting: true},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			// Message from bob
			msgData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-1", "from_agent": "bob", "body": "are you there?",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", msgData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Listen(ctx, mustClient(t, server.URL), "bob", DefaultWait, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if result.Reply != "are you there?" {
		t.Fatalf("reply=%s", result.Reply)
	}
	if result.SessionID != "s1" {
		t.Fatalf("session_id=%s", result.SessionID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			// Send nothing, just keep connection open
			fmt.Fprintf(w, ": keepalive\n\n")
			if flusher != nil {
				flusher.Flush()
			}
			<-time.After(10 * time.Second)
		},
	})
	t.Cleanup(server.Close)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Listen(ctx, mustClient(t, server.URL), "bob", 1, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "timeout" {
		t.Fatalf("status=%s (expected 'timeout')", result.Status)
	}
	if result.WaitedSeconds < 1 {
		t.Fatalf("waited_seconds=%d", result.WaitedSeconds)
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "implementer", DID: "did:aw:implementer", Address: "myteam/implementer"},
					{Alias: "architect", DID: "did:aw:architect", Address: "myteam/architect"},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "implementer", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type":           "message",
				"message_id":     "msg-reply-1",
				"from_stable_id": "did:aw:architect",
				"body":           "hi back!",
				"from_did":       "did:key:z6MkSender",
				"is_contact":     false,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetAddress("myteam/implementer")
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case "myteam/architect":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkSender",
					StableID:    "did:aw:architect",
					Address:     identifier,
					Lifetime:    awid.LifetimeEphemeral,
					Custody:     awid.CustodySelf,
					ResolvedVia: "registry",
				}, nil
			case "myteam/implementer":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkSelf",
					Address:     identifier,
					Lifetime:    awid.LifetimePersistent,
					Custody:     awid.CustodySelf,
					ResolvedVia: "registry",
				}, nil
			default:
				t.Fatalf("identifier=%q", identifier)
				return nil, errors.New("unexpected identifier")
			}
		},
	})

	result, err := Send(context.Background(), client, "implementer", []string{"architect"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d, want 1", len(result.Events))
	}
	if result.Events[0].IsContact != nil {
		t.Fatalf("ephemeral stable-DID SSE sender should suppress contact tag, got %v", *result.Events[0].IsContact)
	}
}

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

	senderPub, senderPriv, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	senderDID := awid.ComputeDIDKey(senderPub)
	stableID := "did:aw:architect"
	sentMsgID := "msg-sent-1"
	replyMsgID := "msg-reply-1"
	replyTimestamp := "2026-04-10T00:00:00Z"

	replyEnv := &awid.MessageEnvelope{
		From:         "myteam/architect",
		FromDID:      senderDID,
		Type:         "chat",
		Body:         "hi back!",
		Timestamp:    replyTimestamp,
		FromStableID: stableID,
		MessageID:    replyMsgID,
	}
	replySig, err := awid.SignMessage(senderPriv, replyEnv)
	if err != nil {
		t.Fatal(err)
	}
	replySignedPayload := awid.CanonicalJSON(replyEnv)

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{})
		},
		"POST /v1/chat/sessions": func(w http.ResponseWriter, r *http.Request) {
			var req awid.ChatCreateSessionRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "implementer", DID: "did:aw:implementer", Address: "myteam/implementer"},
					{Alias: "architect", DID: stableID, Address: "myteam/architect"},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "implementer", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type":           "message",
				"message_id":     replyMsgID,
				"from_agent":     "architect",
				"from_address":   "myteam/architect",
				"body":           "hi back!",
				"from_did":       senderDID,
				"signature":      replySig,
				"signing_key_id": senderDID,
				"signed_payload": replySignedPayload,
				"timestamp":      replyTimestamp,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetAddress("myteam/implementer")
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case "myteam/architect":
				return &awid.ResolvedIdentity{
					DID:         senderDID,
					StableID:    stableID,
					Address:     identifier,
					Lifetime:    awid.LifetimePersistent,
					Custody:     awid.CustodySelf,
					ResolvedVia: "registry",
				}, nil
			case "myteam/implementer":
				return &awid.ResolvedIdentity{
					DID:         "did:key:z6MkSelf",
					Address:     identifier,
					Lifetime:    awid.LifetimePersistent,
					Custody:     awid.CustodySelf,
					ResolvedVia: "registry",
				}, nil
			default:
				t.Fatalf("identifier=%q", identifier)
				return nil, errors.New("unexpected identifier")
			}
		},
		verify: func(_ context.Context, address, gotStableID string) *awid.StableIdentityVerification {
			if address != "myteam/architect" {
				t.Fatalf("address=%q", address)
			}
			if gotStableID != stableID {
				t.Fatalf("stable_id=%q, want %q", gotStableID, stableID)
			}
			return &awid.StableIdentityVerification{
				Outcome:       awid.StableIdentityVerified,
				CurrentDIDKey: "did:key:z6MkDifferentCurrent",
			}
		},
	})

	result, err := Send(context.Background(), client, "implementer", []string{"architect"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d, want 1", len(result.Events))
	}
	if result.Events[0].FromStableID != stableID {
		t.Fatalf("from_stable_id=%q, want %q", result.Events[0].FromStableID, stableID)
	}
	if result.Events[0].VerificationStatus != awid.IdentityMismatch {
		t.Fatalf("verification_status=%s, want identity_mismatch", result.Events[0].VerificationStatus)
	}
}

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

	senderPub, senderPriv, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	senderDID := awid.ComputeDIDKey(senderPub)

	wrongRecipientPub, _, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	wrongRecipientDID := awid.ComputeDIDKey(wrongRecipientPub)

	sentMsgID := "msg-sent-1"
	replyMsgID := "msg-reply-1"
	replyTimestamp := "2026-04-10T00:00:00Z"

	replyEnv := &awid.MessageEnvelope{
		From:      "myteam/architect",
		FromDID:   senderDID,
		To:        "myteam/implementer",
		ToDID:     wrongRecipientDID,
		Type:      "chat",
		Body:      "hi back!",
		Timestamp: replyTimestamp,
		MessageID: replyMsgID,
	}
	replySig, err := awid.SignMessage(senderPriv, replyEnv)
	if err != nil {
		t.Fatal(err)
	}
	replySignedPayload := awid.CanonicalJSON(replyEnv)

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{})
		},
		"POST /v1/chat/sessions": func(w http.ResponseWriter, r *http.Request) {
			var req awid.ChatCreateSessionRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "implementer", DID: "did:aw:implementer", Address: "myteam/implementer"},
					{Alias: "architect", DID: "did:aw:architect", Address: "myteam/architect"},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "implementer", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type":           "message",
				"message_id":     replyMsgID,
				"from_agent":     "architect",
				"from_address":   "myteam/architect",
				"body":           "hi back!",
				"from_did":       senderDID,
				"signature":      replySig,
				"signing_key_id": senderDID,
				"signed_payload": replySignedPayload,
				"timestamp":      replyTimestamp,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	client := mustIdentityClient(t, server.URL)
	client.SetAddress("myteam/implementer")

	result, err := Send(context.Background(), client, "implementer", []string{"architect"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d, want 1", len(result.Events))
	}
	if result.Events[0].ToDID != wrongRecipientDID {
		t.Fatalf("to_did=%q, want %q", result.Events[0].ToDID, wrongRecipientDID)
	}
	if result.Events[0].VerificationStatus != awid.IdentityMismatch {
		t.Fatalf("verification_status=%s, want identity_mismatch", result.Events[0].VerificationStatus)
	}
}

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

	senderPub, senderPriv, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	senderDID := awid.ComputeDIDKey(senderPub)

	receiverOldPub, _, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	receiverOldDID := awid.ComputeDIDKey(receiverOldPub)
	receiverStableID := awid.ComputeStableID(receiverOldPub)

	receiverNewPub, receiverNewPriv, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	receiverNewDID := awid.ComputeDIDKey(receiverNewPub)

	sentMsgID := "msg-sent-rotation"
	replyMsgID := "msg-reply-rotation"
	replyTimestamp := "2026-04-10T00:00:00Z"

	replyEnv := &awid.MessageEnvelope{
		From:       "myteam/architect",
		FromDID:    senderDID,
		To:         receiverStableID,
		ToDID:      receiverOldDID,
		ToStableID: receiverStableID,
		Type:       "chat",
		Body:       "hi after rotation!",
		Timestamp:  replyTimestamp,
		MessageID:  replyMsgID,
	}
	replySig, err := awid.SignMessage(senderPriv, replyEnv)
	if err != nil {
		t.Fatal(err)
	}
	replySignedPayload := awid.CanonicalJSON(replyEnv)

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{})
		},
		"POST /v1/chat/sessions": func(w http.ResponseWriter, r *http.Request) {
			var req awid.ChatCreateSessionRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "implementer", DID: receiverStableID, Address: "myteam/implementer"},
					{Alias: "architect", DID: "did:aw:architect", Address: "myteam/architect"},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "implementer", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type":           "message",
				"message_id":     replyMsgID,
				"from_agent":     "architect",
				"from_address":   "myteam/architect",
				"body":           "hi after rotation!",
				"from_did":       senderDID,
				"signature":      replySig,
				"signing_key_id": senderDID,
				"signed_payload": replySignedPayload,
				"timestamp":      replyTimestamp,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	client, err := awid.NewWithIdentity(server.URL, receiverNewPriv, receiverNewDID)
	if err != nil {
		t.Fatal(err)
	}
	client.SetAddress("myteam/implementer")
	client.SetStableID(receiverStableID)

	result, err := Send(context.Background(), client, "implementer", []string{"architect"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if len(result.Events) != 1 {
		t.Fatalf("events=%d, want 1", len(result.Events))
	}
	if result.Events[0].VerificationStatus != awid.Verified {
		t.Fatalf("verification_status=%s, want verified", result.Events[0].VerificationStatus)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{})
	t.Cleanup(server.Close)

	result, err := waitForMessage(
		context.Background(),
		mustClient(t, server.URL),
		func(context.Context, string, time.Time, *time.Time) (*awid.SSEStream, error) {
			return nil, io.EOF
		},
		"s1",
		nil,
		"",
		1,
		nil,
		nil,
		func(Event) (bool, bool) { return false, false },
	)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "timeout" {
		t.Fatalf("status=%s", result.Status)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/sessions/s1/messages": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatHistoryResponse{Messages: []awid.ChatMessage{}})
		},
	})
	t.Cleanup(server.Close)

	result, err := waitForMessage(
		context.Background(),
		mustClient(t, server.URL),
		func(context.Context, string, time.Time, *time.Time) (*awid.SSEStream, error) {
			return nil, &url.Error{Op: "Get", URL: server.URL + "/v1/chat/sessions/s1/stream", Err: io.EOF}
		},
		"s1",
		nil,
		"",
		1,
		nil,
		nil,
		func(Event) (bool, bool) { return false, false },
	)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "timeout" {
		t.Fatalf("status=%s", result.Status)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{})
	t.Cleanup(server.Close)

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	_, err := waitForMessage(
		ctx,
		mustClient(t, server.URL),
		func(context.Context, string, time.Time, *time.Time) (*awid.SSEStream, error) {
			return nil, context.Canceled
		},
		"s1",
		nil,
		"",
		1,
		nil,
		nil,
		func(Event) (bool, bool) { return false, false },
	)
	if !errors.Is(err, context.Canceled) {
		t.Fatalf("err=%v, want context.Canceled", err)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{})
	t.Cleanup(server.Close)

	_, err := waitForMessage(
		context.Background(),
		mustClient(t, server.URL),
		func(context.Context, string, time.Time, *time.Time) (*awid.SSEStream, error) {
			return nil, &url.Error{Op: "Get", URL: server.URL + "/v1/chat/sessions/s1/stream", Err: io.ErrUnexpectedEOF}
		},
		"s1",
		nil,
		"",
		1,
		nil,
		nil,
		func(Event) (bool, bool) { return false, false },
	)
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "connecting to SSE") {
		t.Fatalf("err=%v", err)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{Sessions: []awid.ChatSessionItem{}})
		},
	})
	t.Cleanup(server.Close)

	_, err := Listen(context.Background(), mustClient(t, server.URL), "nobody", DefaultWait, nil)
	if err == nil {
		t.Fatal("expected error for missing session")
	}
	if !strings.Contains(err.Error(), "no conversation found") {
		t.Fatalf("err=%s", err)
	}
}

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

	var callbackCalls []string

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			// Extend-wait from bob
			hangOnData, _ := json.Marshal(map[string]any{
				"type": "message", "from_agent": "bob",
				"body": "thinking...", "hang_on": true, "extends_wait_seconds": 300,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", hangOnData)
			if flusher != nil {
				flusher.Flush()
			}

			// Actual reply from bob
			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "from_agent": "bob", "body": "here's my answer",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	callback := func(kind, msg string) {
		callbackCalls = append(callbackCalls, kind+": "+msg)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Listen(ctx, mustClient(t, server.URL), "bob", 5, callback)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if result.Reply != "here's my answer" {
		t.Fatalf("reply=%s", result.Reply)
	}

	foundExtendWait := false
	foundExtended := false
	for _, c := range callbackCalls {
		if strings.HasPrefix(c, "extend_wait:") {
			foundExtendWait = true
		}
		if strings.HasPrefix(c, "wait_extended:") {
			foundExtended = true
		}
	}
	if !foundExtendWait {
		t.Fatal("missing extend_wait callback")
	}
	if !foundExtended {
		t.Fatal("missing wait_extended callback")
	}
}

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

	sentMsgID := "msg-sent-1"
	var callbackCalls []string

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			// Extend-wait without extension
			hangOnData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-hangon", "from_agent": "bob",
				"body": "working on it", "hang_on": true,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", hangOnData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply", "from_agent": "bob", "body": "done",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	callback := func(kind, msg string) {
		callbackCalls = append(callbackCalls, kind+": "+msg)
	}

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, callback)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}

	// Should get exactly one extend_wait callback with the body, not a redundant second one
	extendWaitCount := 0
	for _, c := range callbackCalls {
		if strings.HasPrefix(c, "extend_wait:") {
			extendWaitCount++
		}
	}
	if extendWaitCount != 1 {
		t.Fatalf("expected exactly 1 extend_wait callback, got %d: %v", extendWaitCount, callbackCalls)
	}
}

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

	// In a multi-party session [alice, bob, charlie], listening for "bob"
	// should return when charlie sends a message (not filtered by target).
	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob", "charlie"}},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			msgData, _ := json.Marshal(map[string]any{
				"type": "message", "from_agent": "charlie", "body": "hello everyone",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", msgData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Listen(ctx, mustClient(t, server.URL), "bob", DefaultWait, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if result.Reply != "hello everyone" {
		t.Fatalf("reply=%s (expected message from charlie, not filtered)", result.Reply)
	}
}

// --- Fix 3: findSession prefers smallest matching session ---

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s-group", Participants: []string{"alice", "bob", "charlie"}},
					{SessionID: "s-pair", Participants: []string{"alice", "bob"}},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, _, err := findSession(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-pair" {
		t.Fatalf("session_id=%s, want s-pair (smallest matching session)", sessionID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{SessionID: "s-group", Participants: []string{"alice", "bob", "charlie"}},
					{SessionID: "s-pair", Participants: []string{"alice", "bob"}},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, _, err := findSession(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-pair" {
		t.Fatalf("session_id=%s, want s-pair (smallest matching session)", sessionID)
	}
}

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

	// Two same-size sessions: one where sender is waiting, one not.
	// findSession should prefer the one where the sender is waiting.
	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s-idle", Participants: []string{"alice", "bob"}, SenderWaiting: false, LastActivity: "2026-01-01T00:00:02Z"},
					{SessionID: "s-waiting", Participants: []string{"alice", "bob"}, SenderWaiting: true, LastActivity: "2026-01-01T00:00:01Z"},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, senderWaiting, err := findSession(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-waiting" {
		t.Fatalf("session_id=%s, want s-waiting (sender_waiting should take priority)", sessionID)
	}
	if !senderWaiting {
		t.Fatal("sender_waiting=false, want true")
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s-waiting", Participants: []string{"alice", "bob"}, SenderWaiting: true, LastActivity: "2026-01-01T00:00:01Z"},
					{SessionID: "s-recent", Participants: []string{"alice", "bob"}, SenderWaiting: false, LastActivity: "2026-01-01T00:00:05Z"},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, senderWaiting, err := findLatestSession(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-recent" {
		t.Fatalf("session_id=%s, want s-recent (latest selection should prefer activity)", sessionID)
	}
	if senderWaiting {
		t.Fatal("sender_waiting=true, want false from latest active session")
	}
}

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

	// Two same-size, same-waiting-status sessions: prefer most recent activity.
	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s-old", Participants: []string{"alice", "bob"}, LastActivity: "2026-01-01T00:00:01Z"},
					{SessionID: "s-recent", Participants: []string{"alice", "bob"}, LastActivity: "2026-01-01T00:00:05Z"},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, _, err := findSession(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-recent" {
		t.Fatalf("session_id=%s, want s-recent (most recent activity wins tiebreak)", sessionID)
	}
}

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

	// Two same-size sessions in fallback: prefer most recently created.
	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{SessionID: "s-old", Participants: []string{"alice", "bob"}, CreatedAt: "2026-01-01T00:00:01Z"},
					{SessionID: "s-recent", Participants: []string{"alice", "bob"}, CreatedAt: "2026-01-01T00:00:05Z"},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, _, err := findSession(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-recent" {
		t.Fatalf("session_id=%s, want s-recent (most recent created_at wins tiebreak)", sessionID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:    "s-newer-created",
						Participants: []string{"alice", "bob"},
						CreatedAt:    "2026-01-01T00:00:05Z",
						LastActivity: "2026-01-01T00:00:05Z",
					},
					{
						SessionID:    "s-active",
						Participants: []string{"alice", "bob"},
						CreatedAt:    "2026-01-01T00:00:01Z",
						LastActivity: "2026-01-01T00:00:10Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, _, err := findSession(context.Background(), mustClient(t, server.URL), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "s-active" {
		t.Fatalf("session_id=%s, want s-active (most recent activity wins tiebreak)", sessionID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:    "primary-session",
						TeamID:       "devteam:test.local",
						Participants: []string{"bob"},
						CreatedAt:    "2026-01-01T00:00:05Z",
						LastActivity: "2026-01-01T00:00:05Z",
					},
					{
						SessionID:    "partner-session",
						TeamID:       "main:partner.local",
						Participants: []string{"bob"},
						CreatedAt:    "2026-01-01T00:00:01Z",
						LastActivity: "2026-01-01T00:00:01Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, _, err := findLatestSession(context.Background(), mustTeamClient(t, server.URL, "main:partner.local"), "bob")
	if err != nil {
		t.Fatal(err)
	}
	if sessionID != "partner-session" {
		t.Fatalf("session_id=%s, want partner-session", sessionID)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{Pending: []awid.ChatPendingItem{}})
		},
		"GET /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatListSessionsResponse{
				Sessions: []awid.ChatSessionItem{
					{
						SessionID:            "primary-session",
						TeamID:               "devteam:test.local",
						Participants:         []string{"bob"},
						ParticipantAddresses: []string{"test.local/bob"},
						CreatedAt:            "2026-01-01T00:00:05Z",
						LastActivity:         "2026-01-01T00:00:05Z",
					},
				},
			})
		},
	})
	t.Cleanup(server.Close)

	sessionID, _, err := findLatestSession(context.Background(), mustTeamClient(t, server.URL, "main:partner.local"), "partner.local/bob")
	if err == nil {
		t.Fatalf("session_id=%s, want no conversation found", sessionID)
	}
	if sessionID != "" {
		t.Fatalf("session_id=%s, want empty", sessionID)
	}
}

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

	ev := parseSSEEvent(&awid.SSEEvent{
		Event: "message",
		Data:  `{"type":"message","from_agent":"bob","body":"hello","sender_waiting":true}`,
	})
	if !ev.SenderWaiting {
		t.Fatal("sender_waiting=false, want true")
	}

	// false when absent
	ev2 := parseSSEEvent(&awid.SSEEvent{
		Event: "message",
		Data:  `{"type":"message","from_agent":"bob","body":"hello"}`,
	})
	if ev2.SenderWaiting {
		t.Fatal("sender_waiting=true, want false when absent")
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply", "from_agent": "bob",
				"body": "what do you think?", "sender_waiting": true,
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if !result.SenderWaiting {
		t.Fatal("result.SenderWaiting=false, want true from reply event")
	}
}

// --- SSE after parameter and events filtering ---

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

	var receivedAfter string
	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, r *http.Request) {
			receivedAfter = r.URL.Query().Get("after")
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_agent": "bob", "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	beforeSend := time.Now()
	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if receivedAfter == "" {
		t.Fatal("stream URL missing 'after' query parameter")
	}
	afterTime, err := time.Parse(time.RFC3339, receivedAfter)
	if err != nil {
		t.Fatalf("invalid after timestamp (want RFC3339): %s", receivedAfter)
	}
	// after is truncated to seconds and shifted back 1s, so allow 2s tolerance.
	if afterTime.Before(beforeSend.Add(-2 * time.Second)) {
		t.Fatalf("after=%s is too old (before send at %s)", afterTime, beforeSend)
	}
}

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

	var receivedAfter string

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, r *http.Request) {
			receivedAfter = r.URL.Query().Get("after")
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			msgData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-1", "from_agent": "bob", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", msgData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	result, err := Listen(context.Background(), mustClient(t, server.URL), "bob", DefaultWait, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if receivedAfter != "" {
		t.Fatalf("Listen should not pass 'after' param, got: %s", receivedAfter)
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			// Replay: old message (before our sent message — will be skipped)
			oldData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "old-msg", "from_agent": "charlie", "body": "old stuff",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", oldData)

			// Replay: our sent message (will be skipped)
			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)

			// Reply from bob (accepted)
			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_agent": "bob", "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	// Only the reply should be in events, not the skipped replay messages
	if len(result.Events) != 1 {
		t.Fatalf("events count=%d, expected 1 (only the reply)", len(result.Events))
	}
	if result.Events[0].MessageID != "msg-reply-1" {
		t.Fatalf("event[0].message_id=%s, expected msg-reply-1", result.Events[0].MessageID)
	}
}

// TestSendAfterParameterUsesSecondPrecision verifies that the SSE stream
// after parameter is truncated to second precision (minus one second) so the
// server's replay query (WHERE created_at > $after) always includes the sent
// message. Without this, the sequential gate in the acceptor never opens.
func TestSendAfterParameterUsesSecondPrecision(t *testing.T) {
	t.Parallel()

	sentMsgID := "msg-sent-1"
	var afterParam string

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, r *http.Request) {
			afterParam = r.URL.Query().Get("after")

			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			// With truncated after, the sent message IS in the replay.
			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_agent": "bob", "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 2}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s, want replied", result.Status)
	}
	if result.Reply != "hi back!" {
		t.Fatalf("reply=%q, want %q", result.Reply, "hi back!")
	}

	// Verify the after parameter has second precision (no sub-second digits).
	if afterParam == "" {
		t.Fatal("after query parameter was empty")
	}
	if strings.Contains(afterParam, ".") {
		t.Errorf("after param %q has sub-second precision; want second precision", afterParam)
	}
	// Must parse as valid RFC3339.
	if _, err := time.Parse(time.RFC3339, afterParam); err != nil {
		t.Errorf("after param %q is not valid RFC3339: %v", afterParam, err)
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_agent": "bob", "from_address": "otherco/bob", "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"otherco/bob"}, "hello", SendOptions{Wait: 2}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s, want replied", result.Status)
	}
	if result.Reply != "hi back!" {
		t.Fatalf("reply=%q, want %q", result.Reply, "hi back!")
	}
}

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

	sentMsgID := "msg-sent-1"
	targetDID := "did:aw:bob"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:aw:alice", Address: "acme.com/alice"},
					{Alias: "bob", DID: targetDID, Address: "otherco.com/bob"},
				},
				TargetsConnected: []string{"bob"},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_agent": "bob", "from_address": "otherco/bob", "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"otherco/bob"}, "hello", SendOptions{Wait: 2}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.TargetNotConnected {
		t.Fatalf("target_not_connected=%v, want false", result.TargetNotConnected)
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:aw:alice", Address: "acme.com/alice"},
					{Alias: "rose", DID: "did:aw:acme-rose", Address: "acme.com/rose"},
					{Alias: "rose", DID: "did:aw:otherco-rose", Address: "otherco.com/rose"},
				},
				TargetsConnected: []string{"acme.com/rose"},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			<-time.After(10 * time.Second)
		},
	})
	t.Cleanup(server.Close)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Send(ctx, mustClient(t, server.URL), "alice", []string{"otherco.com/rose"}, "hello", SendOptions{Wait: 1}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if !result.TargetNotConnected {
		t.Fatalf("target_not_connected=%v, want true", result.TargetNotConnected)
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:aw:alice", Address: "acme.com/alice"},
					{Alias: "rose", DID: "did:aw:acme-rose", Address: "acme.com/rose"},
					{Alias: "rose", DID: "did:aw:otherco-rose", Address: "otherco.com/rose"},
				},
				TargetsConnected: []string{"acme.com/rose"},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			<-time.After(10 * time.Second)
		},
	})
	t.Cleanup(server.Close)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Send(ctx, mustClient(t, server.URL), "alice", []string{"rose"}, "hello", SendOptions{Wait: 1}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if !result.TargetNotConnected {
		t.Fatalf("target_not_connected=%v, want true", result.TargetNotConnected)
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:aw:alice", Address: "acme.com/alice"},
					{Alias: "rose", DID: "did:aw:acme-rose", Address: "acme.com/rose"},
					{Alias: "rose", DID: "did:aw:otherco-rose", Address: "otherco.com/rose"},
				},
				TargetsConnected: []string{"acme.com/rose"},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type":         "message",
				"message_id":   "msg-reply-1",
				"from_agent":   "rose",
				"from_address": "acme.com/rose",
				"from_did":     "did:aw:acme-rose",
				"body":         "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}

			<-time.After(10 * time.Second)
		},
	})
	t.Cleanup(server.Close)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Send(ctx, mustClient(t, server.URL), "alice", []string{"rose"}, "hello", SendOptions{Wait: 1}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "timeout" {
		t.Fatalf("status=%s, want timeout", result.Status)
	}
	if !result.TargetNotConnected {
		t.Fatalf("target_not_connected=%v, want true", result.TargetNotConnected)
	}
}

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

	sentMsgID := "msg-sent-1"
	targetDID := "did:aw:bob"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:aw:alice", Address: "acme.com/alice"},
					{Alias: "bob", DID: targetDID, Address: "otherco.com/bob"},
				},
				TargetsConnected: []string{"bob"},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_did": targetDID, "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"otherco.com/bob"}, "hello", SendOptions{Wait: 2}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s, want replied", result.Status)
	}
	if result.Reply != "hi back!" {
		t.Fatalf("reply=%q, want %q", result.Reply, "hi back!")
	}
}

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

	sentMsgID := "msg-sent-1"
	targetStableID := "did:aw:bob"
	targetCurrentDID := "did:key:z6MkBobCurrent"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:key:z6MkAliceCurrent"},
					{DID: targetStableID},
				},
				TargetsConnected: []string{targetStableID},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_stable_id": targetStableID, "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case targetStableID, targetCurrentDID:
				return &awid.ResolvedIdentity{DID: targetCurrentDID, StableID: targetStableID}, nil
			default:
				return nil, errors.New("unexpected identifier")
			}
		},
	})

	result, err := Send(context.Background(), client, "alice", []string{targetCurrentDID}, "hello", SendOptions{Wait: 2}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s, want replied", result.Status)
	}
	if result.Reply != "hi back!" {
		t.Fatalf("reply=%q, want %q", result.Reply, "hi back!")
	}
}

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

	sentMsgID := "msg-sent-1"
	targetStableID := "did:aw:bob"
	targetCurrentDID := "did:key:z6MkBobCurrent"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:key:z6MkAliceCurrent"},
					{DID: targetCurrentDID},
				},
				TargetsConnected: []string{targetCurrentDID},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case targetStableID, targetCurrentDID:
				return &awid.ResolvedIdentity{DID: targetCurrentDID, StableID: targetStableID}, nil
			default:
				return nil, errors.New("unexpected identifier")
			}
		},
	})

	result, err := Send(context.Background(), client, "alice", []string{targetStableID}, "hello", SendOptions{Wait: 1}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.TargetNotConnected {
		t.Fatalf("target_not_connected=%v, want false", result.TargetNotConnected)
	}
}

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

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID:   "s1",
				MessageID:   "msg-sent-1",
				TargetsLeft: []string{"bob"},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"otherco/bob"}, "hello", SendOptions{Wait: 1}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "targets_left" {
		t.Fatalf("status=%s, want targets_left", result.Status)
	}
}

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

	sentMsgID := "msg-sent-1"
	targetDID := "did:aw:bob"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:aw:alice", Address: "acme.com/alice"},
					{Alias: "bob", DID: targetDID, Address: "otherco.com/bob"},
				},
				TargetsConnected: []string{"bob"},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_agent": "bob", "from_did": targetDID, "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{targetDID}, "hello", SendOptions{Wait: 2}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.TargetNotConnected {
		t.Fatalf("target_not_connected=%v, want false", result.TargetNotConnected)
	}
}

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

	sentMsgID := "msg-sent-1"
	targetStableID := "did:aw:bob"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:aw:alice", Address: "acme.com/alice"},
					{Alias: "bob", DID: targetStableID, Address: "otherco.com/bob"},
				},
				TargetsConnected: []string{targetStableID},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type":           "message",
				"message_id":     "msg-reply-1",
				"from_agent":     "bob",
				"from_stable_id": "did:aw:mallory",
				"body":           "spoofed",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}

			<-time.After(10 * time.Second)
		},
	})
	t.Cleanup(server.Close)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Send(ctx, mustClient(t, server.URL), "alice", []string{targetStableID}, "hello", SendOptions{Wait: 1}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "timeout" {
		t.Fatalf("status=%s, want timeout", result.Status)
	}
}

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

	sentMsgID := "msg-sent-1"
	targetStableID := "did:aw:monitor"
	targetCurrentDID := "did:key:z6MkMonitor"
	otherStableID := "did:aw:other-monitor"
	otherCurrentDID := "did:key:z6MkOtherMonitor"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
				Participants: []awid.ChatParticipant{
					{Alias: "alice", DID: "did:key:z6MkAliceCurrent", Address: "acme.com/alice"},
					{Alias: "monitor", DID: targetCurrentDID, Address: "acme.com/monitor"},
					{Alias: "monitor", DID: otherCurrentDID, Address: "otherco.com/monitor"},
				},
				TargetsConnected: []string{"otherco.com/monitor"},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			<-time.After(10 * time.Second)
		},
	})
	t.Cleanup(server.Close)

	client := mustClient(t, server.URL)
	client.SetResolver(stubIdentityResolver{
		resolve: func(_ context.Context, identifier string) (*awid.ResolvedIdentity, error) {
			switch identifier {
			case targetStableID, targetCurrentDID, "acme.com/monitor":
				return &awid.ResolvedIdentity{
					DID:         targetCurrentDID,
					StableID:    targetStableID,
					Handle:      "monitor",
					Address:     "acme.com/monitor",
					ResolvedVia: "registry",
				}, nil
			case otherStableID, otherCurrentDID, "otherco.com/monitor":
				return &awid.ResolvedIdentity{
					DID:         otherCurrentDID,
					StableID:    otherStableID,
					Handle:      "monitor",
					Address:     "otherco.com/monitor",
					ResolvedVia: "registry",
				}, nil
			default:
				return nil, errors.New("unexpected identifier")
			}
		},
	})

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Send(ctx, client, "alice", []string{targetStableID}, "hello", SendOptions{Wait: 1}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if !result.TargetNotConnected {
		t.Fatalf("target_not_connected=%v, want true", result.TargetNotConnected)
	}
}

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

	// Generate a real keypair to produce a valid signature.
	pub, priv, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	did := awid.ComputeDIDKey(pub)

	env := &awid.MessageEnvelope{
		From:    "alice",
		FromDID: did,
		Type:    "chat",
		Body:    "signed hello",
	}
	env.Timestamp = "2026-01-01T00:00:00Z"
	sig, err := awid.SignMessage(priv, env)
	if err != nil {
		t.Fatal(err)
	}

	data := fmt.Sprintf(`{"from_agent":"alice","body":"signed hello","from_did":%q,"signature":%q,"signing_key_id":%q,"timestamp":"2026-01-01T00:00:00Z"}`, did, sig, did)

	ev := parseSSEEvent(&awid.SSEEvent{
		Event: "message",
		Data:  data,
	})

	if ev.FromDID != did {
		t.Fatalf("from_did=%s", ev.FromDID)
	}
	if ev.Signature != sig {
		t.Fatalf("signature=%s", ev.Signature)
	}
	if ev.SigningKeyID != did {
		t.Fatalf("signing_key_id=%s", ev.SigningKeyID)
	}
	if ev.VerificationStatus != awid.Verified {
		t.Fatalf("verification_status=%s", ev.VerificationStatus)
	}
}

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

	ev := parseSSEEvent(&awid.SSEEvent{
		Event: "message",
		Data:  `{"from_agent":"bob","body":"unsigned hello"}`,
	})

	if ev.FromDID != "" {
		t.Fatalf("from_did=%s", ev.FromDID)
	}
	if ev.VerificationStatus != awid.Unverified {
		t.Fatalf("verification_status=%s, want %s", ev.VerificationStatus, awid.Unverified)
	}
}

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

	pub, priv, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	did := awid.ComputeDIDKey(pub)
	stableID := "did:aw:stable-sender"
	toDID := "did:key:z6MkpTyWJw55qh8q1UnzsepWtL4yJ534Ezx6Wmgj6mR3zPFk"

	env := &awid.MessageEnvelope{
		From:           "myco/alice",
		FromDID:        did,
		To:             "myco/bob",
		ToDID:          toDID,
		Type:           "chat",
		Body:           "signed hello",
		Timestamp:      "2026-01-01T00:00:00Z",
		FromStableID:   stableID,
		MessageID:      "11111111-1111-4111-8111-111111111111",
		ConversationID: "22222222-2222-4222-8222-222222222222",
	}
	sig, err := awid.SignMessage(priv, env)
	if err != nil {
		t.Fatal(err)
	}
	signedPayload := awid.CanonicalJSON(env)

	// Live stream rows can carry the participant stable DID in from_did while
	// the signed payload carries the signing did:key. The parser must verify
	// against the signed-payload did:key, matching ChatHistory normalization.
	data := fmt.Sprintf(`{"from_agent":"alice","from_address":"myco/alice","to_address":"myco/bob","body":"signed hello","from_did":%q,"from_stable_id":%q,"signature":%q,"signed_payload":%q,"timestamp":"2026-01-01T00:00:00Z"}`, stableID, stableID, sig, signedPayload)

	ev := parseSSEEvent(&awid.SSEEvent{
		Event: "message",
		Data:  data,
	})

	if ev.FromDID != did {
		t.Fatalf("from_did=%q, want signed payload did:key %q", ev.FromDID, did)
	}
	if ev.FromStableID != stableID {
		t.Fatalf("from_stable_id=%q, want %q", ev.FromStableID, stableID)
	}
	if ev.VerificationStatus != awid.Verified {
		t.Fatalf("verification_status=%s, want verified", ev.VerificationStatus)
	}
}

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

	pub, priv, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
	did := awid.ComputeDIDKey(pub)

	// Sign with full address.
	env := &awid.MessageEnvelope{
		From:      "myco/alice",
		FromDID:   did,
		Type:      "chat",
		Body:      "signed hello",
		Timestamp: "2026-01-01T00:00:00Z",
	}
	sig, err := awid.SignMessage(priv, env)
	if err != nil {
		t.Fatal(err)
	}

	// SSE data has from_agent="alice" (alias-only) but from_address="myco/alice".
	data := fmt.Sprintf(`{"from_agent":"alice","from_address":"myco/alice","body":"signed hello","from_did":%q,"signature":%q,"signing_key_id":%q,"timestamp":"2026-01-01T00:00:00Z"}`, did, sig, did)

	ev := parseSSEEvent(&awid.SSEEvent{
		Event: "message",
		Data:  data,
	})

	if ev.FromAddress != "myco/alice" {
		t.Fatalf("from_address=%q, want myco/alice", ev.FromAddress)
	}
	// Verification should succeed because From in envelope uses from_address.
	if ev.VerificationStatus != awid.Verified {
		t.Fatalf("verification_status=%s, want verified (from_address should be used)", ev.VerificationStatus)
	}
}

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

	data := `{"from_agent":"alice","body":"hi","timestamp":"2025-01-01T00:00:00Z","is_contact":true}`
	ev := parseSSEEvent(&awid.SSEEvent{Event: "message", Data: data})
	if ev.IsContact == nil || !*ev.IsContact {
		t.Fatalf("IsContact=%v, want ptr to true", ev.IsContact)
	}

	// false case
	data2 := `{"from_agent":"bob","body":"hey","timestamp":"2025-01-01T00:00:00Z","is_contact":false}`
	ev2 := parseSSEEvent(&awid.SSEEvent{Event: "message", Data: data2})
	if ev2.IsContact == nil || *ev2.IsContact {
		t.Fatalf("IsContact=%v, want ptr to false", ev2.IsContact)
	}

	// absent case
	data3 := `{"from_agent":"carol","body":"yo","timestamp":"2025-01-01T00:00:00Z"}`
	ev3 := parseSSEEvent(&awid.SSEEvent{Event: "message", Data: data3})
	if ev3.IsContact != nil {
		t.Fatalf("IsContact=%v, want nil", ev3.IsContact)
	}
}

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

	yes := true
	msgs := []awid.ChatMessage{
		{MessageID: "m1", FromAgent: "alice", Body: "hi", IsContact: &yes},
		{MessageID: "m2", FromAgent: "bob", Body: "hey", IsContact: nil},
	}
	events := buildMessages(msgs)
	if events[0].IsContact == nil || !*events[0].IsContact {
		t.Fatalf("events[0].IsContact=%v, want ptr to true", events[0].IsContact)
	}
	if events[1].IsContact != nil {
		t.Fatalf("events[1].IsContact=%v, want nil", events[1].IsContact)
	}
}

// --- Fix: send-and-wait marks messages as read when received via SSE ---

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

	sentMsgID := "msg-sent-1"
	var markReadCalled bool
	var markReadUpTo string

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_agent": "bob", "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
		"POST /v1/chat/sessions/s1/read": func(w http.ResponseWriter, r *http.Request) {
			markReadCalled = true
			var req awid.ChatMarkReadRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			markReadUpTo = req.UpToMessageID
			jsonResponse(w, awid.ChatMarkReadResponse{Success: true, MessagesMarked: 1})
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if !markReadCalled {
		t.Fatal("ChatMarkRead was not called after receiving reply via SSE")
	}
	if markReadUpTo != "msg-reply-1" {
		t.Fatalf("mark_read up_to=%s, want msg-reply-1", markReadUpTo)
	}
}

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

	var markReadCalled bool
	var markReadUpTo string

	server := newMockServer(map[string]http.HandlerFunc{
		"GET /v1/chat/pending": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatPendingResponse{
				Pending: []awid.ChatPendingItem{
					{SessionID: "s1", Participants: []string{"alice", "bob"}},
				},
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			msgData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-1", "from_agent": "bob", "body": "are you there?",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", msgData)
			if flusher != nil {
				flusher.Flush()
			}
		},
		"POST /v1/chat/sessions/s1/read": func(w http.ResponseWriter, r *http.Request) {
			markReadCalled = true
			var req awid.ChatMarkReadRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			markReadUpTo = req.UpToMessageID
			jsonResponse(w, awid.ChatMarkReadResponse{Success: true, MessagesMarked: 1})
		},
	})
	t.Cleanup(server.Close)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	result, err := Listen(ctx, mustClient(t, server.URL), "bob", DefaultWait, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if !markReadCalled {
		t.Fatal("ChatMarkRead was not called after receiving message via SSE in Listen")
	}
	if markReadUpTo != "msg-1" {
		t.Fatalf("mark_read up_to=%s, want msg-1", markReadUpTo)
	}
}

func TestSendRetriesMarkReadOnceAfterReply(t *testing.T) {
	sentMsgID := "msg-sent-1"
	var markReadCalls int

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_agent": "bob", "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
		"POST /v1/chat/sessions/s1/read": func(w http.ResponseWriter, r *http.Request) {
			markReadCalls++
			if markReadCalls == 1 {
				http.Error(w, "try again", http.StatusInternalServerError)
				return
			}
			var req awid.ChatMarkReadRequest
			_ = json.NewDecoder(r.Body).Decode(&req)
			if req.UpToMessageID != "msg-reply-1" {
				t.Errorf("up_to_message_id=%s", req.UpToMessageID)
			}
			jsonResponse(w, awid.ChatMarkReadResponse{Success: true, MessagesMarked: 1})
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if markReadCalls != 2 {
		t.Fatalf("mark_read_calls=%d, want 2", markReadCalls)
	}
}

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

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
				SSEURL:    "/v1/chat/sessions/s1/stream",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			if flusher != nil {
				flusher.Flush()
			}

			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_agent": "bob", "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
		"POST /v1/chat/sessions/s1/read": func(w http.ResponseWriter, _ *http.Request) {
			// Server returns error — should not break Send.
			http.Error(w, "internal error", http.StatusInternalServerError)
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatalf("Send should succeed even if mark-read fails: %v", err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	if result.Reply != "hi back!" {
		t.Fatalf("reply=%s", result.Reply)
	}
}

// TestSendAcceptorIgnoresBodyMatchFallback verifies that the replay-skip gate
// uses only message IDs, not body matching. A replayed message without a
// message_id but with the same body as the sent message must NOT open the gate.
func TestSendAcceptorIgnoresBodyMatchFallback(t *testing.T) {
	t.Parallel()

	sentMsgID := "msg-sent-1"

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, _ *http.Request) {
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: sentMsgID,
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)

			// Replayed old message: same sender, same body, but no message_id.
			// This should NOT open the gate.
			oldData, _ := json.Marshal(map[string]any{
				"type": "message", "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", oldData)

			// The actual sent message with the correct ID — opens the gate.
			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": sentMsgID, "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)

			// Reply from bob.
			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply-1", "from_agent": "bob", "body": "hi back!",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	result, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 5}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "replied" {
		t.Fatalf("status=%s", result.Status)
	}
	// Only the reply should be in events — all pre-gate messages must be skipped.
	if len(result.Events) != 1 {
		t.Fatalf("events count=%d, want 1 (only the reply); body-match fallback may have opened the gate early", len(result.Events))
	}
	if result.Events[0].MessageID != "msg-reply-1" {
		t.Fatalf("event[0].message_id=%s, want msg-reply-1", result.Events[0].MessageID)
	}
}

// TestSendPassesWaitSeconds verifies that Send includes wait_seconds in the
// create-session request so the server knows the actual wait duration.
func TestSendPassesWaitSeconds(t *testing.T) {
	t.Parallel()

	var gotWaitSeconds *int

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, r *http.Request) {
			var body map[string]any
			_ = json.NewDecoder(r.Body).Decode(&body)
			if v, ok := body["wait_seconds"].(float64); ok {
				iv := int(v)
				gotWaitSeconds = &iv
			}
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: "msg-1",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)
			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-1", "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply", "from_agent": "bob", "body": "hi",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	_, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 120}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if gotWaitSeconds == nil {
		t.Fatal("wait_seconds not included in create-session request")
	}
	if *gotWaitSeconds != 120 {
		t.Fatalf("wait_seconds=%d, want 120", *gotWaitSeconds)
	}
}

// TestSendPassesWaitSecondsStartConversationUpgrade verifies that when
// StartConversation upgrades the wait from default to 300s, the server
// sees the actual 300s wait, not the nominal value.
func TestSendPassesWaitSecondsStartConversationUpgrade(t *testing.T) {
	t.Parallel()

	var gotWaitSeconds *int

	server := newMockServer(map[string]http.HandlerFunc{
		"POST /v1/chat/sessions": func(w http.ResponseWriter, r *http.Request) {
			var body map[string]any
			_ = json.NewDecoder(r.Body).Decode(&body)
			if v, ok := body["wait_seconds"].(float64); ok {
				iv := int(v)
				gotWaitSeconds = &iv
			}
			jsonResponse(w, awid.ChatCreateSessionResponse{
				SessionID: "s1",
				MessageID: "msg-1",
			})
		},
		"GET /v1/chat/sessions/s1/stream": func(w http.ResponseWriter, _ *http.Request) {
			w.Header().Set("Content-Type", "text/event-stream")
			flusher, _ := w.(http.Flusher)
			sentData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-1", "from_agent": "alice", "body": "hello",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", sentData)
			replyData, _ := json.Marshal(map[string]any{
				"type": "message", "message_id": "msg-reply", "from_agent": "bob", "body": "hi",
			})
			fmt.Fprintf(w, "event: message\ndata: %s\n\n", replyData)
			if flusher != nil {
				flusher.Flush()
			}
		},
	})
	t.Cleanup(server.Close)

	// StartConversation=true, WaitExplicit=false, Wait=120 → should upgrade to 300.
	_, err := Send(context.Background(), mustClient(t, server.URL), "alice", []string{"bob"}, "hello", SendOptions{Wait: 120, StartConversation: true}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if gotWaitSeconds == nil {
		t.Fatal("wait_seconds not included in create-session request")
	}
	if *gotWaitSeconds != 300 {
		t.Fatalf("wait_seconds=%d, want 300 (StartConversation upgrade)", *gotWaitSeconds)
	}
}

// TestParseSSEEventReplyTo verifies that reply_to_message_id is extracted from SSE events.
func TestParseSSEEventReplyTo(t *testing.T) {
	t.Parallel()

	ev := parseSSEEvent(&awid.SSEEvent{
		Event: "message",
		Data:  `{"message_id":"m2","from_agent":"bob","body":"yes","reply_to_message_id":"m1"}`,
	})
	if ev.ReplyToMessageID != "m1" {
		t.Fatalf("reply_to_message_id=%q, want %q", ev.ReplyToMessageID, "m1")
	}
}

// TestBuildMessagesIncludesReplyTo verifies that buildMessages carries
// reply_to_message_id from ChatMessage to Event.
func TestBuildMessagesIncludesReplyTo(t *testing.T) {
	t.Parallel()

	events := buildMessages([]awid.ChatMessage{
		{MessageID: "m2", FromAgent: "bob", Body: "yes", ReplyToMessageID: "m1"},
	})
	if len(events) != 1 {
		t.Fatalf("events=%d", len(events))
	}
	if events[0].ReplyToMessageID != "m1" {
		t.Fatalf("reply_to_message_id=%q, want %q", events[0].ReplyToMessageID, "m1")
	}
}
