package workspace

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	workspaceservice "github.com/tidbcloud/AgentTeam/internal/service/workspace"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/organization"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
	"github.com/tidbcloud/AgentTeam/internal/unread"
)

type recordingMailer struct{}

func (mailer *recordingMailer) SendVerificationEmail(context.Context, string, string) error {
	return nil
}

func (mailer *recordingMailer) SendPasswordResetEmail(context.Context, string, string) error {
	return nil
}

func (mailer *recordingMailer) SendWorkspaceInviteEmail(context.Context, string, string, string) error {
	return nil
}

func (mailer *recordingMailer) SendOrganizationInviteEmail(context.Context, string, string, string) error {
	return nil
}

type fakeWorkspaceService struct {
	authenticated identity.AuthenticatedUser
	workspace     workspace.Summary

	userWorkspaces []workspace.Summary
	invites        []workspace.WorkspaceInvite

	createdInviteEmail   string
	createdInviteMobile  string
	createdInviteID      string
	revokedInviteID      string
	removedMemberUserID  string
	requireManagementHit bool
	updateWorkspaceArgs  []string
	membersPage          workspace.WorkspaceMemberPage
	member               workspace.WorkspaceMember
	offboardingImpact    workspace.WorkspaceMemberOffboardingImpact
	offboardingUserID    string
	lookupMembers        []workspace.WorkspaceMember
	memberOptions        workspace.WorkspaceMemberListOptions
	lookupOptions        workspace.WorkspaceMemberLookupOptions
	memberManagementErr  error
	machineManagementErr error
	updatedMemberRole    []string
	updateMemberRoleErr  error

	ownMachines []workspaceservice.Machine
	allMachines []workspaceservice.Machine
	agents      []agent.AppAgent

	ownMachineOptions        workspaceservice.MachineListOptions
	allMachineOptions        workspaceservice.MachineListOptions
	accountBehaviorSettings  workspaceservice.AccountBehaviorSettings
	updatedAccountBehavior   workspaceservice.AccountBehaviorSettingsUpdate
	channelSidebarPreference workspaceservice.ChannelSidebarPreference
	channelSidebarExists     bool
	channelSidebarGetArgs    []string
	channelSidebarPutArgs    []string
	channelSidebarRevision   *int64

	listAgentsForWorkspaceHit bool
	machineAgentArgs          []string
	machineAgents             []agent.AppAgent

	renameMachineArgs []string
	renameMachineErr  error

	connectToken     string
	exchange         dto.MachineConnectExchangeResponse
	rotatedKey       string
	deletedMachineID string
}

type fakeWorkspaceRealtime struct {
	disconnectedUser        []string
	workspaceMembersUpdated string
	workspaceRoleUpdated    []string
}

func (fake *fakeWorkspaceRealtime) DisconnectWorkspaceUser(workspaceID string, userID string) {
	fake.disconnectedUser = []string{workspaceID, userID}
}

func (fake *fakeWorkspaceRealtime) EmitWorkspaceMembersUpdated(workspaceID string) {
	fake.workspaceMembersUpdated = workspaceID
}

func (fake *fakeWorkspaceRealtime) EmitWorkspaceMemberRoleUpdated(workspaceID string, userID string, role string) {
	fake.workspaceRoleUpdated = []string{workspaceID, userID, role}
}

func (fake *fakeWorkspaceService) AuthenticateSession(_ context.Context, _ string) (identity.AuthenticatedUser, error) {
	return fake.authenticated, nil
}

func (fake *fakeWorkspaceService) CreateMachineConnectToken(_ context.Context, _ string, _ string, _ string, _ string) (string, error) {
	if fake.connectToken != "" {
		return fake.connectToken, nil
	}
	return "connect-token", nil
}

func (fake *fakeWorkspaceService) CreateMachineForWorkspace(_ context.Context, _ string, _ string, name string) (workspaceservice.Machine, string, error) {
	return workspaceservice.Machine{ID: "mach_1", Name: name}, "api-key", nil
}

func (fake *fakeWorkspaceService) CreateMiniappPairingCode(_ context.Context, _ string, _ string, _ string) (workspaceservice.MiniappPairingIssueResult, error) {
	return workspaceservice.MiniappPairingIssueResult{Code: "mpc_test", ExpiresAt: time.Now().Add(10 * time.Minute)}, nil
}

func (fake *fakeWorkspaceService) CreateWorkspaceInvite(_ context.Context, _ string, _ string, email string, _ string, _ time.Time) (string, error) {
	fake.createdInviteEmail = email
	if fake.createdInviteID == "" {
		fake.createdInviteID = "inv_1"
	}
	return fake.createdInviteID, nil
}

func (fake *fakeWorkspaceService) CreateWorkspaceMobileInvite(_ context.Context, _ string, _ string, mobile string, _ string, _ time.Time) (string, error) {
	fake.createdInviteMobile = mobile
	if fake.createdInviteID == "" {
		fake.createdInviteID = "inv_1"
	}
	return fake.createdInviteID, nil
}

func (fake *fakeWorkspaceService) DeleteMachineForWorkspace(_ context.Context, _ string, _ string, machineID string) error {
	fake.deletedMachineID = machineID
	return nil
}

func (fake *fakeWorkspaceService) EvictMachineForWorkspace(_ context.Context, _ string, _ string, _ string) ([]string, error) {
	return []string{"agt_1"}, nil
}

func (fake *fakeWorkspaceService) ExchangeMachineConnectToken(_ context.Context, _ string) (workspaceservice.MachineConnectExchangeResult, error) {
	return workspaceservice.MachineConnectExchangeResult{
		MachineID:   fake.exchange.MachineID,
		APIKey:      fake.exchange.APIKey,
		ServerURL:   fake.exchange.ServerURL,
		WorkspaceID: fake.exchange.WorkspaceID,
	}, nil
}

func (fake *fakeWorkspaceService) ResolveMachineAuth(_ context.Context, apiKey string, serverURL string) (workspaceservice.MachineConnectExchangeResult, error) {
	return workspaceservice.MachineConnectExchangeResult{
		MachineID:   fake.exchange.MachineID,
		APIKey:      apiKey,
		ServerURL:   serverURL,
		WorkspaceID: fake.exchange.WorkspaceID,
	}, nil
}

func (fake *fakeWorkspaceService) GetWorkspaceForUser(_ context.Context, _ string, workspaceID string) (workspace.Summary, error) {
	if fake.workspace.WorkspaceID != "" {
		return fake.workspace, nil
	}
	return workspace.Summary{WorkspaceID: workspaceID, Name: "Workspace"}, nil
}

func (fake *fakeWorkspaceService) GetChannelSidebarPreferenceForUser(_ context.Context, userID string, workspaceID string) (workspaceservice.ChannelSidebarPreference, bool, error) {
	fake.channelSidebarGetArgs = []string{userID, workspaceID}
	return fake.channelSidebarPreference, fake.channelSidebarExists, nil
}

func (fake *fakeWorkspaceService) PutChannelSidebarPreferenceForUser(_ context.Context, userID string, workspaceID string, state workspaceservice.ChannelSidebarPreferenceState, expectedRevision *int64) (workspaceservice.ChannelSidebarPreference, error) {
	fake.channelSidebarPutArgs = []string{userID, workspaceID}
	fake.channelSidebarRevision = expectedRevision
	fake.channelSidebarPreference.State = state
	if expectedRevision == nil {
		fake.channelSidebarPreference.Revision = 1
	} else {
		fake.channelSidebarPreference.Revision = *expectedRevision + 1
	}
	return fake.channelSidebarPreference, nil
}

func (fake *fakeWorkspaceService) AccountBehaviorSettingsForUser(_ context.Context, userID string) (workspaceservice.AccountBehaviorSettings, error) {
	if fake.accountBehaviorSettings.UserID != "" {
		return fake.accountBehaviorSettings, nil
	}
	return workspaceservice.AccountBehaviorSettings{
		UserID:                      userID,
		VisibleReplyContractEnabled: true,
		RuntimeDebugEnabled:         false,
	}, nil
}

func (fake *fakeWorkspaceService) UpdateAccountBehaviorSettingsForUser(_ context.Context, userID string, update workspaceservice.AccountBehaviorSettingsUpdate) (workspaceservice.AccountBehaviorSettings, error) {
	fake.updatedAccountBehavior = update
	runtimeDebugEnabled := false
	if update.RuntimeDebugEnabled != nil {
		runtimeDebugEnabled = *update.RuntimeDebugEnabled
	}
	return workspaceservice.AccountBehaviorSettings{
		UserID:                      userID,
		VisibleReplyContractEnabled: true,
		RuntimeDebugEnabled:         runtimeDebugEnabled,
	}, nil
}

func (fake *fakeWorkspaceService) ListAgentsForWorkspace(_ context.Context, _ string, _ string) ([]agent.AppAgent, error) {
	fake.listAgentsForWorkspaceHit = true
	return fake.agents, nil
}

func (fake *fakeWorkspaceService) ListAgentsForMachineWorkspace(_ context.Context, userID string, workspaceID string, machineID string) ([]agent.AppAgent, error) {
	fake.machineAgentArgs = []string{userID, workspaceID, machineID}
	return fake.machineAgents, nil
}

func (fake *fakeWorkspaceService) ListAllMachinesForWorkspace(_ context.Context, _ string, _ string) ([]workspaceservice.Machine, error) {
	return fake.allMachines, nil
}

func (fake *fakeWorkspaceService) ListAllMachinesForWorkspacePage(_ context.Context, _ string, _ string, options workspaceservice.MachineListOptions) (workspaceservice.MachineListPage, error) {
	fake.allMachineOptions = options
	return workspaceservice.MachineListPage{Machines: fake.allMachines}, nil
}

func (fake *fakeWorkspaceService) ListOwnMachinesForWorkspace(_ context.Context, _ string, _ string) ([]workspaceservice.Machine, error) {
	return fake.ownMachines, nil
}

func (fake *fakeWorkspaceService) ListOwnMachinesForWorkspacePage(_ context.Context, _ string, _ string, options workspaceservice.MachineListOptions) (workspaceservice.MachineListPage, error) {
	fake.ownMachineOptions = options
	return workspaceservice.MachineListPage{Machines: fake.ownMachines}, nil
}

func (fake *fakeWorkspaceService) ListWorkspaceInvitesForUser(_ context.Context, _ string, _ string) ([]workspace.WorkspaceInvite, error) {
	return fake.invites, nil
}

func (fake *fakeWorkspaceService) ListWorkspaceMembersForUserPage(_ context.Context, _ string, _ string, options workspace.WorkspaceMemberListOptions) (workspace.WorkspaceMemberPage, error) {
	fake.memberOptions = options
	return fake.membersPage, nil
}

func (fake *fakeWorkspaceService) GetWorkspaceMemberForUser(_ context.Context, _ string, _ string, _ string) (workspace.WorkspaceMember, error) {
	return fake.member, nil
}

func (fake *fakeWorkspaceService) LookupWorkspaceMembersForUser(_ context.Context, _ string, _ string, options workspace.WorkspaceMemberLookupOptions) ([]workspace.WorkspaceMember, error) {
	fake.lookupOptions = options
	return fake.lookupMembers, nil
}

func (fake *fakeWorkspaceService) WorkspaceMemberOffboardingImpactForUser(_ context.Context, _ string, _ string, targetUserID string) (workspace.WorkspaceMemberOffboardingImpact, error) {
	fake.offboardingUserID = targetUserID
	return fake.offboardingImpact, nil
}

func (fake *fakeWorkspaceService) ListUserWorkspaces(_ context.Context, _ string) ([]workspace.Summary, error) {
	return fake.userWorkspaces, nil
}

func (fake *fakeWorkspaceService) LoadWorkspaceUsageForUser(_ context.Context, _ string, _ string) (workspace.WorkspaceUsage, error) {
	return workspace.WorkspaceUsage{MachineCount: 1}, nil
}

func (fake *fakeWorkspaceService) RenameMachineForWorkspace(_ context.Context, userID, workspaceID, machineID, name string) error {
	fake.renameMachineArgs = []string{userID, workspaceID, machineID, name}
	return fake.renameMachineErr
}

func (fake *fakeWorkspaceService) RequireMachineManagementForUser(_ context.Context, _ string, _ string, _ string) error {
	fake.requireManagementHit = true
	return fake.machineManagementErr
}

func (fake *fakeWorkspaceService) RevokeWorkspaceInviteForUser(_ context.Context, _ string, _ string, inviteID string) error {
	fake.revokedInviteID = inviteID
	return nil
}

func (fake *fakeWorkspaceService) RemoveWorkspaceMemberForUser(_ context.Context, _ string, _ string, userID string) ([]string, error) {
	fake.removedMemberUserID = userID
	return []string{"mac_removed"}, nil
}

func (fake *fakeWorkspaceService) RotateMachineKeyForUser(_ context.Context, _ string, _ string, _ string) (string, error) {
	if fake.rotatedKey != "" {
		return fake.rotatedKey, nil
	}
	return "rotated-key", nil
}

func (fake *fakeWorkspaceService) UpdateWorkspaceNameForUser(_ context.Context, userID string, workspaceID string, name string) (workspace.Summary, error) {
	fake.updateWorkspaceArgs = []string{userID, workspaceID, name}
	return workspace.Summary{
		WorkspaceID:   workspaceID,
		Slug:          "team",
		Name:          name,
		WorkspaceRole: "admin",
		MemberID:      "mem_1",
		PlanKey:       "pro",
	}, nil
}

type snapshotWorkspaceUnreadStore struct {
	snapshots     []unread.Snapshot
	snapshotLoads int
}

func (store *snapshotWorkspaceUnreadStore) UpsertSnapshots(context.Context, []unread.Snapshot) error {
	return nil
}

func (store *snapshotWorkspaceUnreadStore) LoadSnapshotPage(_ context.Context, workspaceID string, memberID string, scopeIDs []string, afterScopeID string, limit int) ([]unread.Snapshot, error) {
	store.snapshotLoads++
	allowed := make(map[string]struct{}, len(scopeIDs))
	for _, scopeID := range scopeIDs {
		allowed[scopeID] = struct{}{}
	}
	page := make([]unread.Snapshot, 0, limit)
	for _, snapshot := range store.snapshots {
		if snapshot.WorkspaceID != workspaceID || snapshot.MemberID != memberID {
			continue
		}
		if len(allowed) > 0 {
			if _, ok := allowed[snapshot.ScopeID]; !ok {
				continue
			}
		} else if snapshot.ScopeID <= afterScopeID {
			continue
		}
		page = append(page, snapshot)
		if len(page) >= limit {
			break
		}
	}
	return page, nil
}

type fakeWorkspaceUnreadReader struct {
	hasUnread map[string]bool
	calls     []string
}

func (fake *fakeWorkspaceUnreadReader) WorkspaceHasUnread(workspaceID string, memberID string) bool {
	key := workspaceID + ":" + memberID
	fake.calls = append(fake.calls, key)
	return fake.hasUnread[key]
}

func (fake *fakeWorkspaceService) UpdateWorkspaceMemberRoleForUser(_ context.Context, actorUserID string, workspaceID string, targetUserID string, role string) error {
	fake.updatedMemberRole = []string{actorUserID, workspaceID, targetUserID, role}
	return fake.updateMemberRoleErr
}

func (fake *fakeWorkspaceService) RequireWorkspaceMemberManagementForUser(context.Context, string, string) error {
	return fake.memberManagementErr
}

type fakeOrganizationProvider struct {
	createdOrganizationID string
	createdName           string
	createdSlug           string
	providers             []organization.OrganizationProviderView
	hostedSelection       organization.OrganizationHostedModelSelectionView
	workspaceOrganization organization.OrganizationSummary
	workspaceOrgArgs      []string
	accessSources         []organization.WorkspaceMemberAccessSourceView
	accessSourceUserIDs   []string
}

func (fake *fakeOrganizationProvider) CreateOrganizationWorkspaceForUser(_ context.Context, _ string, organizationID string, name string, slug string) (organization.OrganizationWorkspaceView, error) {
	fake.createdOrganizationID = organizationID
	fake.createdName = name
	fake.createdSlug = slug
	return organization.OrganizationWorkspaceView{ID: "wsp_new", Slug: slug, Name: name}, nil
}

func (fake *fakeOrganizationProvider) GetHostedModelSelectionForWorkspace(_ context.Context, organizationID string, workspaceID string) (organization.OrganizationHostedModelSelectionView, error) {
	if fake.hostedSelection.OrganizationID != "" {
		return fake.hostedSelection, nil
	}
	return organization.OrganizationHostedModelSelectionView{
		OrganizationID:         organizationID,
		WorkspaceID:            workspaceID,
		PickerVisibilityPolicy: "show_disabled_when_unavailable",
		AuthorizationMode:      "workspace_not_authorized",
		AuthorizationStatus:    "inactive",
	}, nil
}

func (fake *fakeOrganizationProvider) GetWorkspaceOrganizationForUser(_ context.Context, userID string, workspaceID string) (organization.OrganizationSummary, error) {
	fake.workspaceOrgArgs = []string{userID, workspaceID}
	if fake.workspaceOrganization.ID != "" {
		return fake.workspaceOrganization, nil
	}
	return organization.OrganizationSummary{ID: "org_1", Slug: "team", Name: "Team", Role: "org_owner", DefaultTeamID: "orgt_1"}, nil
}

func (fake *fakeOrganizationProvider) ListWorkspaceMemberAccessSources(context.Context, string) ([]organization.WorkspaceMemberAccessSourceView, error) {
	return fake.accessSources, nil
}

func (fake *fakeOrganizationProvider) ListWorkspaceMemberAccessSourcesForUsers(_ context.Context, _ string, userIDs []string) ([]organization.WorkspaceMemberAccessSourceView, error) {
	fake.accessSourceUserIDs = append([]string(nil), userIDs...)
	return fake.accessSources, nil
}

func (fake *fakeOrganizationProvider) ListVisibleOrganizationProvidersForWorkspace(context.Context, string) ([]organization.OrganizationProviderView, error) {
	return fake.providers, nil
}

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

	fake := &fakeWorkspaceService{
		memberManagementErr: api.ErrForbidden,
		membersPage: workspace.WorkspaceMemberPage{Members: []workspace.WorkspaceMember{
			{UserID: "usr_member", MemberID: "mem_member", DisplayName: "Member"},
		}},
	}
	orgs := &fakeOrganizationProvider{
		accessSources: []organization.WorkspaceMemberAccessSourceView{
			{UserID: "usr_member", Sources: []string{"org-direct"}},
		},
	}
	handler := &WorkspaceHandler{workspaceService: fake, organizations: orgs}
	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/members", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_viewer"}})
	}, handleWorkspaceAuth(handler.Members))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/members", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Empty(t, orgs.accessSourceUserIDs)
	var body dto.WorkspaceMemberPageResponse
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &body))
	require.Len(t, body.Members, 1)
	require.Empty(t, body.Members[0].AccessSources)
}

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

	fake := &fakeWorkspaceService{
		membersPage: workspace.WorkspaceMemberPage{Members: []workspace.WorkspaceMember{}},
	}
	handler := &WorkspaceHandler{workspaceService: fake}
	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/members/search", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_viewer"}})
	}, handleWorkspaceAuthJSON(handler.MembersSearch))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_1/members/search", bytes.NewBufferString(`{
		"limit": 20,
		"query": " alice ",
		"excludeUserIds": ["usr_granted", "usr_owner"]
	}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, workspace.WorkspaceMemberListOptions{
		Limit:          20,
		Query:          "alice",
		ExcludeUserIDs: []string{"usr_granted", "usr_owner"},
	}, fake.memberOptions)
}

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

	unread := &fakeWorkspaceUnreadReader{hasUnread: map[string]bool{"wsp_1:mem_1": true}}
	handler := &WorkspaceHandler{workspaceService: &fakeWorkspaceService{
		userWorkspaces: []workspace.Summary{{WorkspaceID: "wsp_1", Slug: "one", Name: "One", WorkspaceRole: "owner", MemberID: "mem_1", PlanKey: "max"}},
	}, unreadRuntime: unread}
	router := gin.New()
	router.GET("/api/v1/workspaces", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuth(handler.List))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.JSONEq(t, `[{"id":"wsp_1","slug":"one","name":"One","role":"owner","memberId":"mem_1","plan":"max","hasUnread":true,"unreadCount":1}]`, recorder.Body.String())
	require.Equal(t, []string{"wsp_1:mem_1"}, unread.calls)
}

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

	runtime, err := unread.New(unread.Config{HydrateBatchSize: 2})
	require.NoError(t, err)
	store := &snapshotWorkspaceUnreadStore{snapshots: []unread.Snapshot{{
		WorkspaceID:        "wsp_1",
		MemberID:           "mem_1",
		ScopeID:            "tsk_workspace_unread",
		MessageUnreadCount: 1,
		ServingState:       unread.ServingStateSnapshotServing,
	}}}
	service, err := unread.NewService(runtime, store, nil)
	require.NoError(t, err)
	handler := &WorkspaceHandler{workspaceService: &fakeWorkspaceService{
		userWorkspaces: []workspace.Summary{{WorkspaceID: "wsp_1", Slug: "one", Name: "One", WorkspaceRole: "owner", MemberID: "mem_1", PlanKey: "max"}},
	}, unreadRuntime: service}
	router := gin.New()
	router.GET("/api/v1/workspaces", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuth(handler.List))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.JSONEq(t, `[{"id":"wsp_1","slug":"one","name":"One","role":"owner","memberId":"mem_1","plan":"max","hasUnread":true,"unreadCount":1}]`, recorder.Body.String())
	require.Equal(t, 1, store.snapshotLoads)
}

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

	unread := &fakeWorkspaceUnreadReader{hasUnread: map[string]bool{"wsp_1:mem_1": true}}
	handler := &WorkspaceHandler{workspaceService: &fakeWorkspaceService{
		userWorkspaces: []workspace.Summary{{WorkspaceID: "wsp_1", Slug: "one", Name: "One", WorkspaceRole: "owner", MemberID: "mem_1", PlanKey: "max"}},
	}, unreadRuntime: unread}
	router := gin.New()
	router.GET("/api/v1/workspaces", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuth(handler.List))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces?includeUnread=false", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.JSONEq(t, `[{"id":"wsp_1","slug":"one","name":"One","role":"owner","memberId":"mem_1","plan":"max","hasUnread":false}]`, recorder.Body.String())
	require.Empty(t, unread.calls)
}

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

	service := &fakeWorkspaceService{
		userWorkspaces: []workspace.Summary{
			{WorkspaceID: "wsp_1", Slug: "one", Name: "One", WorkspaceRole: "owner", MemberID: "mem_1", PlanKey: "max"},
			{WorkspaceID: "wsp_2", Slug: "two", Name: "Two", WorkspaceRole: "member", MemberID: "mem_2", PlanKey: "free"},
		},
	}
	handler := &WorkspaceHandler{workspaceService: service}
	router := gin.New()
	router.GET("/api/v1/workspaces", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuth(handler.List))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.JSONEq(t, `[
		{"id":"wsp_1","slug":"one","name":"One","role":"owner","memberId":"mem_1","plan":"max","hasUnread":false},
		{"id":"wsp_2","slug":"two","name":"Two","role":"member","memberId":"mem_2","plan":"free","hasUnread":false}
	]`, recorder.Body.String())
}

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

	fake := &fakeWorkspaceService{}
	handler := &WorkspaceHandler{workspaceService: fake}
	router := gin.New()
	router.PATCH("/api/v1/workspaces/:workspaceId", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuthJSON[dto.WorkspaceUpdateRequest](handler.Update))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPatch, "/api/v1/workspaces/wsp_1", bytes.NewBufferString(`{"name":"  Renamed Workspace  "}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, []string{"usr_1", "wsp_1", "Renamed Workspace"}, fake.updateWorkspaceArgs)
	require.JSONEq(t, `{
		"id":"wsp_1",
		"slug":"team",
		"name":"Renamed Workspace",
		"role":"admin",
		"memberId":"mem_1",
		"plan":"pro",
		"hasUnread":false
	}`, recorder.Body.String())
}

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

	fake := &fakeWorkspaceService{}
	unread := &fakeWorkspaceUnreadReader{hasUnread: map[string]bool{"wsp_1:mem_1": true}}
	handler := &WorkspaceHandler{workspaceService: fake, unreadRuntime: unread}
	router := gin.New()
	router.PATCH("/api/v1/workspaces/:workspaceId", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuthJSON[dto.WorkspaceUpdateRequest](handler.Update))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPatch, "/api/v1/workspaces/wsp_1", bytes.NewBufferString(`{"name":"  Renamed Workspace  "}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, []string{"usr_1", "wsp_1", "Renamed Workspace"}, fake.updateWorkspaceArgs)
	require.Equal(t, []string{"wsp_1:mem_1"}, unread.calls)
	require.JSONEq(t, `{
		"id":"wsp_1",
		"slug":"team",
		"name":"Renamed Workspace",
		"role":"admin",
		"memberId":"mem_1",
		"plan":"pro",
		"hasUnread":true,
		"unreadCount":1
	}`, recorder.Body.String())
}

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

	fake := &fakeWorkspaceService{}
	handler := &WorkspaceHandler{workspaceService: fake}
	router := gin.New()
	router.PATCH("/api/v1/workspaces/:workspaceId", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuthJSON[dto.WorkspaceUpdateRequest](handler.Update))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPatch, "/api/v1/workspaces/wsp_1", bytes.NewBufferString(`{"name":"   "}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	require.Contains(t, recorder.Body.String(), "name is required")
	require.Empty(t, fake.updateWorkspaceArgs)
}

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

	handler := &WorkspaceHandler{}
	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/invites", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuthJSON[dto.WorkspaceInviteCreateRequest](handler.InvitesCreate))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_1/invites", bytes.NewBufferString(`{"mobile":"not-a-mobile"}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusBadRequest, recorder.Code)
}

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

	fake := &fakeWorkspaceService{
		member: workspace.WorkspaceMember{UserID: "usr_target", MemberID: "mem_target"},
	}
	realtime := &fakeWorkspaceRealtime{}
	handler := &WorkspaceHandler{workspaceService: fake, realtime: realtime}
	router := gin.New()
	router.PATCH("/api/v1/workspaces/:workspaceId/members/:memberId", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_actor"}})
	}, handleWorkspaceAuthJSON[dto.WorkspaceMemberRoleUpdateRequest](handler.MembersUpdate))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(
		http.MethodPatch,
		"/api/v1/workspaces/wsp_1/members/mem_target",
		bytes.NewBufferString(`{"role":"admin"}`),
	)
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, []string{"usr_actor", "wsp_1", "usr_target", "admin"}, fake.updatedMemberRole)
	require.Empty(t, realtime.workspaceMembersUpdated)
	require.Equal(t, []string{"wsp_1", "usr_target", "admin"}, realtime.workspaceRoleUpdated)
}

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

	fake := &fakeWorkspaceService{
		member:              workspace.WorkspaceMember{UserID: "usr_target", MemberID: "mem_target"},
		updateMemberRoleErr: api.ErrForbidden,
	}
	realtime := &fakeWorkspaceRealtime{}
	handler := &WorkspaceHandler{workspaceService: fake, realtime: realtime}
	router := gin.New()
	router.PATCH("/api/v1/workspaces/:workspaceId/members/:memberId", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_actor"}})
	}, handleWorkspaceAuthJSON[dto.WorkspaceMemberRoleUpdateRequest](handler.MembersUpdate))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(
		http.MethodPatch,
		"/api/v1/workspaces/wsp_1/members/mem_target",
		bytes.NewBufferString(`{"role":"admin"}`),
	)
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusForbidden, recorder.Code)
	require.Equal(t, []string{"usr_actor", "wsp_1", "usr_target", "admin"}, fake.updatedMemberRole)
	require.Empty(t, realtime.workspaceMembersUpdated)
	require.Empty(t, realtime.workspaceRoleUpdated)
}

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

	fake := &fakeWorkspaceService{
		member: workspace.WorkspaceMember{UserID: "usr_target", MemberID: "mem_target"},
	}
	runtime := &fakeMachineRuntime{}
	realtime := &fakeWorkspaceRealtime{}
	handler := &WorkspaceHandler{workspaceService: fake, machineRuntime: runtime, realtime: realtime}
	router := gin.New()
	router.DELETE("/api/v1/workspaces/:workspaceId/members/:memberId", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_actor"}})
	}, handleWorkspaceAuth(handler.MembersDelete))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodDelete, "/api/v1/workspaces/wsp_1/members/mem_target", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, "usr_target", fake.removedMemberUserID)
	require.Equal(t, "mac_removed", runtime.disconnected)
	require.Equal(t, []string{"wsp_1", "usr_target"}, realtime.disconnectedUser)
	require.Equal(t, "wsp_1", realtime.workspaceMembersUpdated)
}

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

	fake := &fakeWorkspaceService{
		offboardingImpact: workspace.WorkspaceMemberOffboardingImpact{MachineCount: 2, AgentCount: 3},
	}
	handler := &WorkspaceHandler{workspaceService: fake}
	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/members/by-user/:userId/offboarding-impact", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_actor"}})
	}, handleWorkspaceAuth(handler.MemberOffboardingImpact))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/members/by-user/usr_target/offboarding-impact", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.JSONEq(t, `{"machineCount":2,"agentCount":3}`, recorder.Body.String())
	require.Equal(t, "usr_target", fake.offboardingUserID)
}

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

	fake := &fakeWorkspaceService{
		workspace:            workspace.Summary{WorkspaceID: "wsp_1", Name: "Team"},
		createdInviteID:      "inv_1",
		authenticated:        identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}},
		userWorkspaces:       []workspace.Summary{{WorkspaceID: "wsp_1"}},
		ownMachines:          []workspaceservice.Machine{},
		allMachines:          []workspaceservice.Machine{},
		connectToken:         "connect-token",
		rotatedKey:           "rotated-key",
		requireManagementHit: false,
	}
	handler := &WorkspaceHandler{workspaceService: fake}
	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/invites", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuthJSON[dto.WorkspaceInviteCreateRequest](handler.InvitesCreate))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_1/invites", bytes.NewBufferString(`{"mobile":"13800138000"}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, "+8613800138000", fake.createdInviteMobile)
	require.Empty(t, fake.revokedInviteID)
}

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

	fake := &fakeWorkspaceService{
		workspace:       workspace.Summary{WorkspaceID: "wsp_1", Name: "Team"},
		createdInviteID: "inv_1",
		authenticated:   identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}},
	}
	handler := &WorkspaceHandler{
		workspaceService: fake,
		mailer:           &recordingMailer{},
		publicBaseURL:    "https://agentteam.example.com/app",
	}
	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/invites", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuthJSON[dto.WorkspaceInviteCreateRequest](handler.InvitesCreate))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_1/invites", bytes.NewBufferString(`{"mobile":"13800138000"}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Contains(t, recorder.Body.String(), `"inviteId":"inv_1"`)
	require.Contains(t, recorder.Body.String(), `"inviteLink":"https://agentteam.example.com/app/invite/inv_1"`)
	require.Equal(t, "+8613800138000", fake.createdInviteMobile)
}

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

	fake := &fakeWorkspaceService{
		authenticated: identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}},
		invites: []workspace.WorkspaceInvite{{
			ID:                "inv_1",
			InvitedMobileHint: "+8613*******00",
			CreatedAt:         time.Date(2026, 5, 8, 12, 0, 0, 0, time.UTC),
		}},
	}
	handler := &WorkspaceHandler{
		workspaceService: fake,
		publicBaseURL:    "https://agentteam.example.com/app",
	}
	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/invites", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuth(handler.InvitesList))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/invites", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.JSONEq(t, `[
		{
			"id": "inv_1",
			"invitedMobileHint": "+8613*******00",
			"createdAt": "2026-05-08T12:00:00Z",
			"inviteLink": "https://agentteam.example.com/app/invite/inv_1"
		}
	]`, recorder.Body.String())
}

func TestWorkspaceV1ListRoute(t *testing.T) {
	router := gin.New()
	fake := &fakeWorkspaceService{
		authenticated: identity.AuthenticatedUser{User: identity.User{ID: "usr_1", EmailVerifiedAt: &[]time.Time{time.Now()}[0]}},
		workspace:     workspace.Summary{WorkspaceID: "wsp_1"},
		userWorkspaces: []workspace.Summary{{
			WorkspaceID: "wsp_1",
			Name:        "Workspace One",
		}},
		membersPage: workspace.WorkspaceMemberPage{Members: []workspace.WorkspaceMember{{MemberID: "mem_1", DisplayName: "Alice"}}},
	}
	RegisterRoutes(router, Dependencies{AuthStore: fake, WorkspaceService: fake})

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces", nil)
	request.Header.Set("Authorization", "Bearer session-token")
	router.ServeHTTP(recorder, request)
	require.Equal(t, http.StatusOK, recorder.Code)
	require.Contains(t, recorder.Body.String(), "Workspace One")
}

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

	type outcome struct {
		status int
		body   string
		args   []string
	}
	run := func(method, path string) outcome {
		fake := &fakeWorkspaceService{
			authenticated: identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}},
		}
		router := gin.New()
		RegisterRoutes(router, Dependencies{AuthStore: fake, WorkspaceService: fake})
		recorder := httptest.NewRecorder()
		request := httptest.NewRequest(method, path, bytes.NewBufferString(`{"name":"  New Name  "}`))
		request.Header.Set("Content-Type", "application/json")
		request.Header.Set("Authorization", "Bearer session-token")
		router.ServeHTTP(recorder, request)
		return outcome{status: recorder.Code, body: recorder.Body.String(), args: fake.renameMachineArgs}
	}
	patch := run(http.MethodPatch, "/api/v1/workspaces/wsp_1/machines/mac_1")
	post := run(http.MethodPost, "/api/v1/workspaces/wsp_1/machines/mac_1/rename")
	require.Equal(t, patch, post)
	require.Equal(t, []string{"usr_1", "wsp_1", "mac_1", "  New Name  "}, post.args)
}

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

	router := gin.New()
	RegisterRoutes(router, Dependencies{})

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_1/machines/mac_1/rename", bytes.NewBufferString(`{"name":"New"}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusUnauthorized, recorder.Code)
}

// TestAccountBehaviorSettingsGETPinsAlwaysOnForHistoricalFalse asserts that the
// account GET handler pins visibleReplyContractEnabled=true in the response even
// when the underlying service still reports a historical false value.
func TestAccountBehaviorSettingsGETPinsAlwaysOnForHistoricalFalse(t *testing.T) {
	t.Parallel()

	fake := &fakeWorkspaceService{
		accountBehaviorSettings: workspaceservice.AccountBehaviorSettings{
			UserID:                      "usr_1",
			VisibleReplyContractEnabled: false,
			RuntimeDebugEnabled:         false,
		},
	}
	handler := &WorkspaceHandler{workspaceService: fake}
	router := gin.New()
	router.GET("/api/v1/me/behavior-settings", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuth(handler.AccountBehaviorSettings))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api/v1/me/behavior-settings", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	var response dto.AccountBehaviorSettingsResponse
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response))
	require.True(t, response.VisibleReplyContractEnabled, "always-on reply enhancement must pin visible=true despite historical false")
}

// TestAccountBehaviorSettingsPATCHVisibleOnlyIsAlwaysOnNoOp asserts that a
// visible-only PATCH (visibleReplyContractEnabled=false, no runtimeDebug) is a
// successful no-op: the response pins visible=true and the service captures no
// visible write (the handler never reaches Update for a runtimeDebug-less body).
func TestAccountBehaviorSettingsPATCHVisibleOnlyIsAlwaysOnNoOp(t *testing.T) {
	t.Parallel()

	fake := &fakeWorkspaceService{
		accountBehaviorSettings: workspaceservice.AccountBehaviorSettings{
			UserID:                      "usr_1",
			VisibleReplyContractEnabled: false,
			RuntimeDebugEnabled:         false,
		},
	}
	handler := &WorkspaceHandler{workspaceService: fake}
	router := gin.New()
	router.PATCH("/api/v1/me/behavior-settings", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuthJSON[dto.AccountBehaviorSettingsUpdateRequest](handler.UpdateAccountBehaviorSettings))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPatch, "/api/v1/me/behavior-settings", bytes.NewBufferString(`{"visibleReplyContractEnabled":false}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	var response dto.AccountBehaviorSettingsResponse
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response))
	require.True(t, response.VisibleReplyContractEnabled, "always-on pins visible=true for visible-only patch")
	require.False(t, response.RuntimeDebugEnabled, "runtimeDebug stays unchanged")
	// visible-only patch never reaches Update: no visible write and no runtime write captured.
	require.Nil(t, fake.updatedAccountBehavior.VisibleReplyContractEnabled, "no visible write reaches the service")
	require.Nil(t, fake.updatedAccountBehavior.RuntimeDebugEnabled, "no runtime write reaches the service")
}

// TestAccountBehaviorSettingsPATCHRuntimeDebugPersistsWithoutVisibleWrite
// asserts that when runtimeDebug=true is sent alongside a historical
// visibleReplyContractEnabled=false, the response still pins visible=true while
// runtimeDebug=true is persisted, and the captured update carries only the
// runtime field with no visible write.
func TestAccountBehaviorSettingsPATCHRuntimeDebugPersistsWithoutVisibleWrite(t *testing.T) {
	t.Parallel()

	fake := &fakeWorkspaceService{
		accountBehaviorSettings: workspaceservice.AccountBehaviorSettings{
			UserID:                      "usr_1",
			VisibleReplyContractEnabled: false,
		},
	}
	handler := &WorkspaceHandler{workspaceService: fake}
	router := gin.New()
	router.PATCH("/api/v1/me/behavior-settings", func(ctx *gin.Context) {
		api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
	}, handleWorkspaceAuthJSON[dto.AccountBehaviorSettingsUpdateRequest](handler.UpdateAccountBehaviorSettings))

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPatch, "/api/v1/me/behavior-settings", bytes.NewBufferString(`{"visibleReplyContractEnabled":false,"runtimeDebugEnabled":true}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	var response dto.AccountBehaviorSettingsResponse
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response))
	require.True(t, response.VisibleReplyContractEnabled, "always-on pins visible=true even when runtimeDebug is persisted")
	require.True(t, response.RuntimeDebugEnabled, "runtimeDebug=true is persisted")
	// The captured update must carry only runtimeDebug; the visible input is dropped.
	require.Nil(t, fake.updatedAccountBehavior.VisibleReplyContractEnabled, "no visible write reaches the service")
	require.NotNil(t, fake.updatedAccountBehavior.RuntimeDebugEnabled)
	require.True(t, *fake.updatedAccountBehavior.RuntimeDebugEnabled, "runtimeDebug=true is the captured write")
}
