package webagent

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/testkit/httputil"
	"github.com/tidbcloud/AgentTeam/internal/runtimecontrol"
	"github.com/tidbcloud/AgentTeam/internal/service/maas"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

type fakeAgentResourceCopier struct {
	calls  int
	last   AgentResourceCopyRequest
	result AgentResourceCopyResult
	err    error
}

func (fake *fakeAgentResourceCopier) CopyAgentResources(_ context.Context, request AgentResourceCopyRequest) (AgentResourceCopyResult, error) {
	fake.calls++
	fake.last = request
	if fake.err != nil {
		return AgentResourceCopyResult{}, fake.err
	}
	return fake.result, nil
}

type countingDuplicateStore struct {
	*fakeStore
	sourceReadCalls int
	createCalls     int
	roleCopyCalls   int
}

func (fake *countingDuplicateStore) GetAgentForUser(ctx context.Context, userID, workspaceID, agentID string) (agent.AgentView, error) {
	fake.sourceReadCalls++
	return fake.fakeStore.GetAgentForUser(ctx, userID, workspaceID, agentID)
}

func (fake *countingDuplicateStore) CreateAgentForUser(ctx context.Context, userID, workspaceID, machineID string, params agent.CreateAgentParams) (agent.AgentView, error) {
	fake.createCalls++
	return fake.fakeStore.CreateAgentForUser(ctx, userID, workspaceID, machineID, params)
}

func (fake *countingDuplicateStore) CopyAgentRoleBindingForUser(ctx context.Context, userID, workspaceID, sourceAgentID, targetAgentID string) error {
	fake.roleCopyCalls++
	return fake.fakeStore.CopyAgentRoleBindingForUser(ctx, userID, workspaceID, sourceAgentID, targetAgentID)
}

func newDuplicateStore() *fakeStore {
	fake := newFakeStore()
	fake.agentView = agent.AgentView{
		AgentID:         "agt_source",
		DisplayName:     "Source Agent",
		Description:     "source desc",
		Runtime:         "codex",
		Model:           "gpt-5.4",
		ReasoningEffort: "medium",
		LLMMode:         agent.AgentLLMModeLocal,
		BaseURL:         "https://llm.example",
		APIKey:          "secret",
		Headers:         map[string]string{"X-Test": "1"},
		WireAPI:         "responses",
		MachineID:       "mach_source",
		SourceKind:      "my-role",
		SourceRef:       "role_1",
	}
	fake.appAgents["agt_source"] = agent.AppAgent{
		ID:          "agt_source",
		Name:        "source-agent",
		DisplayName: "Source Agent",
		AvatarURL:   "https://avatar.example/a.png",
		MachineID:   "mach_source",
	}
	fake.createdAgent = agent.AgentView{AgentID: "agt_new", MachineID: "mach_source"}
	fake.appAgents["agt_new"] = agent.AppAgent{
		ID:          "agt_new",
		Name:        "source-copy",
		DisplayName: "Source Copy",
		MachineID:   "mach_source",
	}
	return fake
}

func duplicateRequest(overrides map[string]any) map[string]any {
	request := map[string]any{
		"mode":        "config_only",
		"name":        "source-copy",
		"displayName": "Source Copy",
	}
	for key, value := range overrides {
		request[key] = value
	}
	return request
}

func duplicateRuntime(machineID string, runtimeName string) *fakeRuntime {
	return &fakeRuntime{
		connectedMachines: map[string]bool{machineID: true},
		runtimeRuntimes:   map[string][]string{machineID: {runtimeName}},
	}
}

func TestDuplicateAgent(t *testing.T) {
	t.Run("inherits source target and copies config and role binding", func(t *testing.T) {
		fake := newDuplicateStore()
		fake.agentView.AutoWakeEnabled = false
		copier := &fakeAgentResourceCopier{}
		realtime := &fakeRealtime{}
		runtime := &fakeRuntime{
			connectedMachines: map[string]bool{"mach_source": true},
			runtimeRuntimes:   map[string][]string{"mach_source": {"codex"}},
		}
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, ResourceCopy: copier, Runtime: runtime, Realtime: realtime})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(map[string]any{
			"channelIds": []string{"chn_1"},
		}))
		require.Equal(t, http.StatusOK, resp.Code)
		require.False(t, fake.createdAgent.AutoWakeEnabled)

		require.Equal(t, "mach_source", fake.lastMachineAccessCheck)
		require.Equal(t, "mach_source", fake.lastCreate.MachineID)
		require.Equal(t, "source-copy", fake.lastCreate.Params.Name)
		require.Equal(t, "Source Copy", fake.lastCreate.Params.DisplayName)
		require.Equal(t, "source desc", fake.lastCreate.Params.Description)
		require.Equal(t, "https://avatar.example/a.png", fake.lastCreate.Params.AvatarURL)
		require.Equal(t, "codex", fake.lastCreate.Params.Runtime)
		require.Equal(t, agent.AgentLLMModeLocal, fake.lastCreate.Params.LLMMode)
		require.Equal(t, "secret", fake.lastCreate.Params.APIKey)
		require.Equal(t, "my-role", fake.lastCreate.Params.SourceKind)
		require.Equal(t, "role_1", fake.lastCreate.Params.SourceRef)
		require.NotNil(t, fake.lastCreate.Params.AutoWakeEnabled)
		require.False(t, *fake.lastCreate.Params.AutoWakeEnabled)
		require.Equal(t, []string{"chn_1"}, fake.lastCreate.Params.ChannelIDs)
		require.Equal(t, copyRoleBindingCall{SourceAgentID: "agt_source", TargetAgentID: "agt_new"}, fake.lastCopyRoleBinding)
		require.Zero(t, copier.calls)
		require.Equal(t, []identityCall{{MachineID: "mach_source", AgentID: "agt_new", Name: "source-copy", DisplayName: "Source Copy"}}, runtime.identityCalls)
		require.Equal(t, []string{"mach_source"}, runtime.syncMachineCalls)
		require.Equal(t, []string{"mach_source"}, runtime.catalogMachineCalls)
		require.Equal(t, "wsp_1", realtime.createdWorkspace)

		var out dto.WebAgentDuplicateResponse
		require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out))
		require.Equal(t, "agt_new", out.Agent.ID)
		require.Nil(t, out.ResourceCopy)
	})

	t.Run("uses explicit target machine and runtime", func(t *testing.T) {
		fake := newDuplicateStore()
		fake.createdAgent.MachineID = "mach_target"
		fake.appAgents["agt_new"] = agent.AppAgent{ID: "agt_new", Name: "target-copy", DisplayName: "Target Copy", MachineID: "mach_target"}
		runtime := &fakeRuntime{
			connectedMachines: map[string]bool{"mach_target": true},
			runtimeRuntimes:   map[string][]string{"mach_target": {"claude"}},
		}
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, Runtime: runtime})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(map[string]any{
			"name": "target-copy", "displayName": "Target Copy", "machineId": " mach_target ", "runtime": " claude ",
		}))
		require.Equal(t, http.StatusOK, resp.Code)
		require.Equal(t, "mach_target", fake.lastMachineAccessCheck)
		require.Equal(t, "mach_target", fake.lastCreate.MachineID)
		require.Equal(t, "claude", fake.lastCreate.Params.Runtime)
		require.Equal(t, "responses", fake.lastCreate.Params.WireAPI, "local provider config is copied without server-side runtime rewriting")
		require.Equal(t, []identityCall{{MachineID: "mach_target", AgentID: "agt_new", Name: "target-copy", DisplayName: "Target Copy"}}, runtime.identityCalls)
		require.Equal(t, []string{"mach_target"}, runtime.syncMachineCalls)
	})

	t.Run("falls back machine and runtime independently", func(t *testing.T) {
		testCases := []struct {
			name            string
			request         map[string]any
			catalog         map[string][]string
			createdMachine  string
			expectedMachine string
			expectedRuntime string
		}{
			{
				name:            "machine override inherits runtime",
				request:         map[string]any{"machineId": "mach_target", "runtime": " "},
				catalog:         map[string][]string{"mach_target": {"codex"}},
				createdMachine:  "mach_target",
				expectedMachine: "mach_target",
				expectedRuntime: "codex",
			},
			{
				name:            "runtime override inherits machine",
				request:         map[string]any{"machineId": " ", "runtime": "claude"},
				catalog:         map[string][]string{"mach_source": {"claude"}},
				createdMachine:  "mach_source",
				expectedMachine: "mach_source",
				expectedRuntime: "claude",
			},
		}
		for _, testCase := range testCases {
			t.Run(testCase.name, func(t *testing.T) {
				fake := newDuplicateStore()
				fake.agentView.WireAPI = ""
				fake.createdAgent.MachineID = testCase.createdMachine
				fake.appAgents["agt_new"] = agent.AppAgent{ID: "agt_new", MachineID: testCase.createdMachine}
				runtime := &fakeRuntime{
					connectedMachines: map[string]bool{testCase.expectedMachine: true},
					runtimeRuntimes:   testCase.catalog,
				}
				router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, Runtime: runtime})

				resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(testCase.request))
				require.Equal(t, http.StatusOK, resp.Code)
				require.Equal(t, testCase.expectedMachine, fake.lastCreate.MachineID)
				require.Equal(t, testCase.expectedRuntime, fake.lastCreate.Params.Runtime)
			})
		}
	})

	t.Run("rejects target machine without management permission before create", func(t *testing.T) {
		fake := newDuplicateStore()
		fake.machineAccessErr = storeerr.ErrForbidden
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, Runtime: duplicateRuntime("mach_target", "codex")})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(map[string]any{
			"machineId": "mach_target",
		}))
		require.Equal(t, http.StatusForbidden, resp.Code)
		require.Equal(t, "mach_target", fake.lastMachineAccessCheck)
		require.Empty(t, fake.lastCreate.UserID)
	})

	t.Run("fails closed when runtime catalog validation is not configured", func(t *testing.T) {
		fake := newDuplicateStore()
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(nil))
		require.Equal(t, http.StatusServiceUnavailable, resp.Code)
		require.Contains(t, resp.Body.String(), "runtime catalog validation")
		require.Empty(t, fake.lastCreate.UserID)
	})

	t.Run("rejects offline target before create", func(t *testing.T) {
		fake := newDuplicateStore()
		runtime := &fakeRuntime{connectedMachines: map[string]bool{"mach_source": false}}
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, Runtime: runtime})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(nil))
		require.Equal(t, http.StatusServiceUnavailable, resp.Code)
		require.Contains(t, resp.Body.String(), runtimecontrol.ErrMachineOffline.Error())
		require.Empty(t, fake.lastCreate.UserID)
	})

	t.Run("rejects unavailable target runtime catalog before create", func(t *testing.T) {
		testCases := []struct {
			name       string
			catalog    map[string][]string
			wantStatus int
			wantDetail string
		}{
			{name: "unknown catalog", wantStatus: http.StatusServiceUnavailable, wantDetail: "runtime catalog"},
			{name: "runtime missing", catalog: map[string][]string{"mach_source": {"codex"}}, wantStatus: http.StatusConflict, wantDetail: "claude"},
		}
		for _, testCase := range testCases {
			t.Run(testCase.name, func(t *testing.T) {
				fake := newDuplicateStore()
				fake.agentView.WireAPI = ""
				runtime := &fakeRuntime{
					connectedMachines: map[string]bool{"mach_source": true},
					runtimeRuntimes:   testCase.catalog,
				}
				router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, Runtime: runtime})

				resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(map[string]any{
					"runtime": "claude",
				}))
				require.Equal(t, testCase.wantStatus, resp.Code)
				require.Contains(t, resp.Body.String(), testCase.wantDetail)
				require.Empty(t, fake.lastCreate.UserID)
			})
		}
	})

	t.Run("preserves reasoning effort for target runtime", func(t *testing.T) {
		for _, effort := range []string{"max", "ultra", "xhigh"} {
			t.Run(effort, func(t *testing.T) {
				fake := newDuplicateStore()
				fake.agentView.Runtime = "claude"
				fake.agentView.ReasoningEffort = effort
				fake.agentView.WireAPI = ""
				runtime := &fakeRuntime{
					connectedMachines: map[string]bool{"mach_source": true},
					runtimeRuntimes:   map[string][]string{"mach_source": {"codex"}},
				}
				router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, Runtime: runtime})

				resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(map[string]any{
					"runtime": "codex",
				}))
				require.Equal(t, http.StatusOK, resp.Code)
				require.Equal(t, effort, fake.lastCreate.Params.ReasoningEffort)
			})
		}
	})

	t.Run("validates hosted binding against target pair and capability", func(t *testing.T) {
		fake := newDuplicateStore()
		fake.agentView.LLMMode = agent.AgentLLMModeAgentTeamHosted
		fake.agentView.Model = "agentteam/deepseek-v4-pro"
		fake.agentView.WireAPI = "responses"
		fake.createdAgent.MachineID = "mach_target"
		fake.appAgents["agt_new"] = agent.AppAgent{ID: "agt_new", MachineID: "mach_target"}
		supported := true
		runtime := &fakeRuntime{
			connectedMachines:   map[string]bool{"mach_target": true},
			runtimeRuntimes:     map[string][]string{"mach_target": {"claude"}},
			maasDirectByMachine: map[string]*bool{"mach_target": &supported},
		}
		hosted := &fakeHostedAgentValidator{}
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, HostedAgents: hosted, Runtime: runtime})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(map[string]any{
			"machineId": "mach_target", "runtime": "claude",
		}))
		require.Equal(t, http.StatusOK, resp.Code)
		require.Equal(t, 1, hosted.calls)
		require.Equal(t, "claude", hosted.last.Runtime)
		require.NotNil(t, hosted.last.MachineSupportsDirectMaaS)
		require.True(t, *hosted.last.MachineSupportsDirectMaaS)
		require.Equal(t, []string{"mach_target"}, runtime.maasDirectMachineCalls)
		require.Equal(t, "mach_target", fake.lastCreate.MachineID)
		require.Equal(t, "claude", fake.lastCreate.Params.Runtime)
		require.Equal(t, agent.AgentLLMModeAgentTeamHosted, fake.lastCreate.Params.LLMMode)
	})

	t.Run("with resources delegates bounded resource copy", func(t *testing.T) {
		fake := newDuplicateStore()
		fake.authenticated.Session.ID = "sess_web"
		fake.authenticated.Session.UserID = fake.authenticated.User.ID
		copier := &fakeAgentResourceCopier{result: AgentResourceCopyResult{Skills: 2, KnowledgeBases: 1, Memory: true}}
		router := newWebAgentRouter(Dependencies{
			AgentStore:            fake,
			ReviewStore:           fake,
			ResourceCopy:          copier,
			KnowledgeBasesEnabled: true,
			Runtime:               duplicateRuntime("mach_source", "codex"),
		})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(map[string]any{
			"mode": "with_resources",
		}))
		require.Equal(t, http.StatusOK, resp.Code)
		require.Equal(t, 1, copier.calls)
		require.Equal(t, "wsp_1", copier.last.WorkspaceID)
		require.Equal(t, "agt_source", copier.last.Source.ID)
		require.Equal(t, "agt_new", copier.last.Target.ID)
		require.True(t, copier.last.CopyKnowledgeBases)
		require.Equal(t, fake.authenticated.User, copier.last.Identity.User)
		require.Equal(t, "sess_web", copier.last.Identity.SessionID)
		require.Empty(t, copier.last.Identity.InternalPurpose)

		var out dto.WebAgentDuplicateResponse
		require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out))
		require.NotNil(t, out.ResourceCopy)
		require.Equal(t, 2, out.ResourceCopy.Skills)
		require.Equal(t, 1, out.ResourceCopy.KnowledgeBases)
		require.True(t, out.ResourceCopy.Memory)
	})

	t.Run("role binding failure rolls back created target agent", func(t *testing.T) {
		fake := newDuplicateStore()
		fake.createdAgent.MachineID = "mach_target"
		fake.copyRoleBindingErr = errors.New("role copy failed")
		fake.deleteMachine = "mach_target"
		runtime := duplicateRuntime("mach_target", "codex")
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, Runtime: runtime})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(map[string]any{
			"machineId": "mach_target",
		}))
		require.Equal(t, http.StatusInternalServerError, resp.Code)
		require.Equal(t, "agt_new", fake.lastDeleteID)
		require.Equal(t, []agentCall{{MachineID: "mach_target", AgentID: "agt_new"}}, runtime.stopCalls)
		require.Equal(t, []string{"mach_target"}, runtime.syncMachineCalls)
	})

	t.Run("resource copy failure rolls back created target agent", func(t *testing.T) {
		fake := newDuplicateStore()
		fake.createdAgent.MachineID = "mach_target"
		fake.deleteMachine = "mach_target"
		copier := &fakeAgentResourceCopier{err: errors.New("copy failed")}
		runtime := duplicateRuntime("mach_target", "codex")
		router := newWebAgentRouter(Dependencies{
			AgentStore:   fake,
			ReviewStore:  fake,
			ResourceCopy: copier,
			Runtime:      runtime,
		})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(map[string]any{
			"mode": "with_resources", "machineId": "mach_target",
		}))
		require.Equal(t, http.StatusInternalServerError, resp.Code)
		require.Equal(t, "agt_new", fake.lastDeleteID)
		require.Equal(t, []agentCall{{MachineID: "mach_target", AgentID: "agt_new"}}, runtime.stopCalls)
		require.Equal(t, []string{"mach_target"}, runtime.syncMachineCalls)
	})

	t.Run("final response hydration failure rolls back the created target agent", func(t *testing.T) {
		fake := newDuplicateStore()
		fake.createdAgent.MachineID = "mach_target"
		fake.deleteMachine = "mach_target"
		fake.appAgentErrByID = map[string]error{"agt_new": errors.New("hydrate failed")}
		runtime := duplicateRuntime("mach_target", "codex")
		router := newWebAgentRouter(Dependencies{
			AgentStore:  fake,
			ReviewStore: fake,
			Runtime:     runtime,
		})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(map[string]any{
			"machineId": "mach_target",
		}))
		require.Equal(t, http.StatusInternalServerError, resp.Code)
		require.Equal(t, "agt_new", fake.lastDeleteID)
		require.Equal(t, []agentCall{{MachineID: "mach_target", AgentID: "agt_new"}}, runtime.stopCalls)
		require.Equal(t, []string{"mach_target"}, runtime.syncMachineCalls)
	})

	t.Run("surfaces rollback failure instead of hiding an orphaned agent", func(t *testing.T) {
		fake := newDuplicateStore()
		fake.copyRoleBindingErr = errors.New("role copy failed")
		fake.deleteErr = errors.New("delete failed")
		router := newWebAgentRouter(Dependencies{
			AgentStore:  fake,
			ReviewStore: fake,
			Runtime:     duplicateRuntime("mach_source", "codex"),
		})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(nil))
		require.Equal(t, http.StatusInternalServerError, resp.Code)
		require.Contains(t, resp.Body.String(), "rollback failed")
	})

	t.Run("rejects removed blank mode", func(t *testing.T) {
		fake := newDuplicateStore()
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, Runtime: duplicateRuntime("mach_source", "codex")})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(map[string]any{
			"mode": "blank",
		}))
		require.Equal(t, http.StatusBadRequest, resp.Code)
		require.Empty(t, fake.lastCreate.UserID)
	})

	t.Run("classifies name conflict for safe batch retry", func(t *testing.T) {
		fake := newDuplicateStore()
		fake.createErr = agent.MarkNameConflict(storeerr.Conflict("an agent with that name already exists in this workspace"))
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, Runtime: duplicateRuntime("mach_source", "codex")})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(nil))
		require.Equal(t, http.StatusConflict, resp.Code)

		var out struct {
			Code string `json:"code"`
		}
		require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out))
		require.Equal(t, "agent_name_conflict", out.Code)
	})

	t.Run("hosted admission errors remain conflicts", func(t *testing.T) {
		fake := newDuplicateStore()
		fake.agentView.LLMMode = agent.AgentLLMModeAgentTeamHosted
		fake.agentView.Model = "agentteam/deepseek-v4-pro"
		fake.agentView.WireAPI = ""
		runtime := &fakeRuntime{
			connectedMachines: map[string]bool{"mach_source": true},
			runtimeRuntimes:   map[string][]string{"mach_source": {"codex"}},
		}
		hosted := &fakeHostedAgentValidator{err: storeerr.Conflict(maas.ReasonZeroBalance)}
		router := newWebAgentRouter(Dependencies{AgentStore: fake, ReviewStore: fake, HostedAgents: hosted, Runtime: runtime})

		resp := httputil.DoJSON(t, router, http.MethodPost, "/api/v1/workspaces/wsp_1/agents/agt_source/duplicate", duplicateRequest(nil))
		require.Equal(t, http.StatusConflict, resp.Code)
		require.Contains(t, resp.Body.String(), maas.ReasonZeroBalance)
		require.Empty(t, fake.lastCreate.UserID)

		var out struct {
			Code string `json:"code"`
		}
		require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out))
		require.Empty(t, out.Code, "unrelated conflicts must not be classified as safe name-conflict retries")
	})
}

func TestDuplicateAgentResourceCopyIdentityPreflight(t *testing.T) {
	t.Run("with resources rejects missing or mismatched identity before reads and side effects", func(t *testing.T) {
		testCases := []struct {
			name     string
			identity AgentResourceCopyIdentity
		}{
			{name: "missing"},
			{name: "mismatched", identity: AgentResourceCopyIdentity{User: fakeResourceCopyUser("usr_other")}},
		}
		for _, testCase := range testCases {
			t.Run(testCase.name, func(t *testing.T) {
				store := &countingDuplicateStore{fakeStore: newDuplicateStore()}
				copier := &fakeAgentResourceCopier{}
				svc := NewAgentService(
					store, nil, duplicateRuntime("mach_source", "codex"), nil, nil,
					store, nil, nil, copier, false, nil, nil,
				)

				_, _, err := svc.DuplicateAgent(context.Background(), "usr_1", "wsp_1", "agt_source", DuplicateAgentInput{
					Mode:        agentDuplicateModeWithResource,
					Name:        "source-copy",
					DisplayName: "Source Copy",
					Identity:    testCase.identity,
				})

				require.ErrorIs(t, err, storeerr.ErrForbidden)
				require.Zero(t, store.sourceReadCalls)
				require.Zero(t, store.createCalls)
				require.Zero(t, store.roleCopyCalls)
				require.Zero(t, copier.calls)
				require.Empty(t, store.lastMachineAccessCheck)
			})
		}
	})

	t.Run("config only succeeds without resource copy identity", func(t *testing.T) {
		store := &countingDuplicateStore{fakeStore: newDuplicateStore()}
		svc := NewAgentService(
			store, nil, duplicateRuntime("mach_source", "codex"), nil, nil,
			store, nil, nil, nil, false, nil, nil,
		)

		created, resourceCopy, err := svc.DuplicateAgent(context.Background(), "usr_1", "wsp_1", "agt_source", DuplicateAgentInput{
			Mode:        agentDuplicateModeConfigOnly,
			Name:        "source-copy",
			DisplayName: "Source Copy",
		})

		require.NoError(t, err)
		require.Equal(t, "agt_new", created.ID)
		require.Nil(t, resourceCopy)
		require.Equal(t, 1, store.sourceReadCalls)
		require.Equal(t, 1, store.createCalls)
		require.Equal(t, 1, store.roleCopyCalls)
	})
}
