package agent

import (
	"context"
	"errors"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

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

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/service/agentapproval"
	"github.com/tidbcloud/AgentTeam/internal/service/approvalapp"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/agentops"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

type ownerCallCount struct {
	agentManagedByUser int
	machineManagement  int
}

type fakeManagementOwner struct {
	ownerUserID      string
	target           agent.AgentView
	err              error
	getErr           error
	machineErr       error
	crossOwnerAgents map[string]string
	calls            *ownerCallCount
}

func (o fakeManagementOwner) AgentManagedByUser(_ context.Context, _ string, agentID string) (string, error) {
	if o.calls != nil {
		o.calls.agentManagedByUser++
	}
	if owner, ok := o.crossOwnerAgents[agentID]; ok {
		return owner, nil
	}
	return o.ownerUserID, o.err
}
func (o fakeManagementOwner) GetAgentForUser(context.Context, string, string, string) (agent.AgentView, error) {
	if o.getErr != nil {
		return o.target, o.getErr
	}
	return o.target, o.err
}
func (o fakeManagementOwner) RequireMachineManagementForUser(context.Context, string, string, string) error {
	if o.calls != nil {
		o.calls.machineManagement++
	}
	return o.machineErr
}

type fakeApprovalRuntime struct {
	replayResult  approvalapp.SubmitResult
	replayFound   bool
	replayErr     error
	replayCount   int
	requestType   string
	submitRequest approvalapp.SubmitCommand
	submitResult  approvalapp.SubmitResult
	submitErr     error
	submitCount   int
	presentCount  int
	presentErr    error
}

func (runtime *fakeApprovalRuntime) LookupRequestReplay(_ context.Context, _ string, _ approvalapp.SubmitCommand) (approvalapp.SubmitResult, bool, error) {
	runtime.replayCount++
	return runtime.replayResult, runtime.replayFound, runtime.replayErr
}

func (runtime *fakeApprovalRuntime) SubmitRequest(_ context.Context, requestType string, command approvalapp.SubmitCommand) (approvalapp.SubmitResult, error) {
	runtime.submitCount++
	runtime.requestType = requestType
	runtime.submitRequest = command
	return runtime.submitResult, runtime.submitErr
}

func (runtime *fakeApprovalRuntime) PresentPending(_ context.Context, _ approvalapp.SubmitResult) (app.AppNotificationResult, error) {
	runtime.presentCount++
	return app.AppNotificationResult{}, runtime.presentErr
}

func intPtr(v int) *int { return &v }

func newAgentManagementTestGin(correlationID string) *gin.Context {
	ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
	request := httptest.NewRequest(http.MethodPost, "/", nil)
	if correlationID != "" {
		request.Header.Set("X-Request-ID", correlationID)
	}
	ctx.Request = request
	return ctx
}

func TestAgentManagementCreateHappyMapping(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner"}
	approvals := &fakeApprovalRuntime{submitResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_1", State: approval.StatePending}}}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	resp, err := handler.Create(api.AgentScopedJSON[agentManagementCreateRequest]{
		AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin("corr_1"), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
		Body:               agentManagementCreateRequest{RequestID: "req_1", Name: "new-agent", MachineID: "mac_1", Runtime: "codex"},
	})
	require.NoError(t, err)
	body, ok := resp.Body.(agentManagementResponse)
	require.True(t, ok)
	require.Equal(t, "pending", body.Status)
	require.Equal(t, "appr_1", body.ApprovalID)
	require.Equal(t, 1, approvals.presentCount)
	require.Equal(t, "req_1", approvals.submitRequest.ClientRequestID)
	require.Equal(t, agentapproval.RequestType, approvals.requestType)
	submit, ok := approvals.submitRequest.Payload.(agentapproval.SubmitInput)
	require.True(t, ok)
	require.Equal(t, agentapproval.ActionCreate, submit.Payload.Action)
	require.NotNil(t, submit.Payload.Create)
	require.Equal(t, "new-agent", submit.Payload.Create.Name)
	require.Equal(t, "mac_1", submit.Payload.Create.MachineID)
	require.Equal(t, "codex", submit.Payload.Create.Runtime)
}

func TestAgentManagementUpdateAbsentNullValuePointerSemantics(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner"}
	approvals := &fakeApprovalRuntime{submitResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_upd", State: approval.StatePending}}}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	modelValue := "gpt-5"
	_, err := handler.Update(api.AgentScopedJSON[agentManagementUpdateRequest]{
		AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
		Body: agentManagementUpdateRequest{
			RequestID: "req_upd", AgentID: "agt_00000000target",
			Description:     dto.OptionalString{Set: false},
			Model:           dto.OptionalString{Set: true, Value: nil},
			ReasoningEffort: dto.OptionalString{Set: true, Value: &modelValue},
			ModelProfileID:  dto.OptionalString{Set: false},
		},
	})
	require.NoError(t, err)
	submit, ok := approvals.submitRequest.Payload.(agentapproval.SubmitInput)
	require.True(t, ok)
	require.NotNil(t, submit.Payload.Update)
	require.Equal(t, "agt_00000000target", submit.Payload.Update.AgentID)
	require.Nil(t, submit.Payload.Update.Description)
	require.NotNil(t, submit.Payload.Update.Model)
	require.Equal(t, "", *submit.Payload.Update.Model)
	require.NotNil(t, submit.Payload.Update.ReasoningEffort)
	require.Equal(t, "gpt-5", *submit.Payload.Update.ReasoningEffort)
	require.Nil(t, submit.Payload.Update.ModelProfileID)
}

func TestAgentManagementDeletePreservesInputOrder(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner"}
	approvals := &fakeApprovalRuntime{submitResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_del", State: approval.StatePending}}}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	_, err := handler.Delete(api.AgentScopedJSON[agentManagementDeleteRequest]{
		AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
		Body:               agentManagementDeleteRequest{RequestID: "req_del", AgentIDs: []string{"agt_0000000000000z", "agt_0000000000000a", "agt_0000000000000m"}},
	})
	require.NoError(t, err)
	submit, ok := approvals.submitRequest.Payload.(agentapproval.SubmitInput)
	require.True(t, ok)
	require.NotNil(t, submit.Payload.Delete)
	require.Equal(t, []string{"agt_0000000000000z", "agt_0000000000000a", "agt_0000000000000m"}, submit.Payload.Delete.AgentIDs)
}

func TestAgentManagementDuplicateHappyMapping(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner"}
	approvals := &fakeApprovalRuntime{submitResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_dup", State: approval.StatePending}}}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	_, err := handler.Duplicate(api.AgentScopedJSON[agentManagementDuplicateRequest]{
		AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
		Body:               agentManagementDuplicateRequest{RequestID: "req_dup", SourceAgentID: "agt_00000000000src", Count: intPtr(3), Mode: "with_resources"},
	})
	require.NoError(t, err)
	submit, ok := approvals.submitRequest.Payload.(agentapproval.SubmitInput)
	require.True(t, ok)
	require.NotNil(t, submit.Payload.Duplicate)
	require.Equal(t, agentapproval.ActionDuplicate, submit.Payload.Action)
	require.Equal(t, "agt_00000000000src", submit.Payload.Duplicate.SourceAgentID)
	require.Equal(t, 3, submit.Payload.Duplicate.Count)
	require.Equal(t, "with_resources", submit.Payload.Duplicate.Mode)
}

func TestAgentManagementDuplicateOmittedCountDefaultsToOne(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner"}
	approvals := &fakeApprovalRuntime{submitResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_dup", State: approval.StatePending}}}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	resp, err := handler.Duplicate(api.AgentScopedJSON[agentManagementDuplicateRequest]{
		AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
		Body:               agentManagementDuplicateRequest{RequestID: "req_dup", SourceAgentID: "agt_00000000000src"},
	})
	require.NoError(t, err)
	body, ok := resp.Body.(agentManagementResponse)
	require.True(t, ok)
	require.Equal(t, "pending", body.Status)
	submit, ok := approvals.submitRequest.Payload.(agentapproval.SubmitInput)
	require.True(t, ok)
	require.Equal(t, 1, submit.Payload.Duplicate.Count)
}

func TestAgentManagementDuplicateInvalidCountRejectedBeforeSubmit(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner"}
	approvals := &fakeApprovalRuntime{}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	for _, count := range []int{0, 11} {
		_, err := handler.Duplicate(api.AgentScopedJSON[agentManagementDuplicateRequest]{
			AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
			Body:               agentManagementDuplicateRequest{RequestID: "req_dup", SourceAgentID: "agt_00000000000src", Count: intPtr(count)},
		})
		require.Error(t, err)
		require.Equal(t, 0, approvals.submitCount, "invalid count must not submit")
	}
}

func TestAgentManagementInvalidShapeRejectedBeforeOwnerAndSubmit(t *testing.T) {
	identity := managementRouteIdentity()
	cases := []struct {
		name   string
		action func(*AgentManagementHandler) error
	}{
		{"create missing name", func(h *AgentManagementHandler) error {
			_, err := h.Create(api.AgentScopedJSON[agentManagementCreateRequest]{
				AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: identity},
				Body:               agentManagementCreateRequest{RequestID: "r", MachineID: "mac", Runtime: "rt"},
			})
			return err
		}},
		{"create missing runtime", func(h *AgentManagementHandler) error {
			_, err := h.Create(api.AgentScopedJSON[agentManagementCreateRequest]{
				AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: identity},
				Body:               agentManagementCreateRequest{RequestID: "r", Name: "n", MachineID: "mac"},
			})
			return err
		}},
		{"update no change", func(h *AgentManagementHandler) error {
			_, err := h.Update(api.AgentScopedJSON[agentManagementUpdateRequest]{
				AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: identity},
				Body:               agentManagementUpdateRequest{RequestID: "r", AgentID: "a"},
			})
			return err
		}},
		{"delete too many", func(h *AgentManagementHandler) error {
			ids := make([]string, 11)
			for i := range ids {
				ids[i] = "a"
			}
			_, err := h.Delete(api.AgentScopedJSON[agentManagementDeleteRequest]{
				AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: identity},
				Body:               agentManagementDeleteRequest{RequestID: "r", AgentIDs: ids},
			})
			return err
		}},
		{"delete blank id", func(h *AgentManagementHandler) error {
			_, err := h.Delete(api.AgentScopedJSON[agentManagementDeleteRequest]{
				AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: identity},
				Body:               agentManagementDeleteRequest{RequestID: "r", AgentIDs: []string{"   "}},
			})
			return err
		}},
		{"duplicate count 0", func(h *AgentManagementHandler) error {
			_, err := h.Duplicate(api.AgentScopedJSON[agentManagementDuplicateRequest]{
				AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: identity},
				Body:               agentManagementDuplicateRequest{RequestID: "r", SourceAgentID: "a", Count: intPtr(0)},
			})
			return err
		}},
		{"duplicate count 11", func(h *AgentManagementHandler) error {
			_, err := h.Duplicate(api.AgentScopedJSON[agentManagementDuplicateRequest]{
				AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: identity},
				Body:               agentManagementDuplicateRequest{RequestID: "r", SourceAgentID: "a", Count: intPtr(11)},
			})
			return err
		}},
		{"duplicate invalid mode", func(h *AgentManagementHandler) error {
			_, err := h.Duplicate(api.AgentScopedJSON[agentManagementDuplicateRequest]{
				AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: identity},
				Body:               agentManagementDuplicateRequest{RequestID: "r", SourceAgentID: "a", Count: intPtr(1), Mode: "bogus"},
			})
			return err
		}},
		{"duplicate empty source", func(h *AgentManagementHandler) error {
			_, err := h.Duplicate(api.AgentScopedJSON[agentManagementDuplicateRequest]{
				AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: identity},
				Body:               agentManagementDuplicateRequest{RequestID: "r", Count: intPtr(1)},
			})
			return err
		}},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			calls := &ownerCallCount{}
			approvals := &fakeApprovalRuntime{}
			handler := &AgentManagementHandler{owners: fakeManagementOwner{ownerUserID: "usr", calls: calls}, approvals: approvals}
			err := tc.action(handler)
			require.Error(t, err)
			require.Equal(t, 0, calls.agentManagedByUser, "no owner lookup before validation")
			require.Equal(t, 0, calls.machineManagement, "no machine check before validation")
			require.Equal(t, 0, approvals.submitCount, "no submit before validation")
		})
	}
}

func TestAgentManagementCreateMachineErrorNoSubmit(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner", machineErr: storeerr.Forbidden("not your machine")}
	approvals := &fakeApprovalRuntime{}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	_, err := handler.Create(api.AgentScopedJSON[agentManagementCreateRequest]{
		AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
		Body:               agentManagementCreateRequest{RequestID: "req_1", Name: "n", MachineID: "mac", Runtime: "r"},
	})
	require.Error(t, err)
	require.Nil(t, approvals.submitRequest.Payload)
}

func TestAgentManagementUpdateCrossOwnerNoSubmit(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner", crossOwnerAgents: map[string]string{"agt_000000000other": "usr_000000000other"}}
	approvals := &fakeApprovalRuntime{}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	_, err := handler.Update(api.AgentScopedJSON[agentManagementUpdateRequest]{
		AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
		Body:               agentManagementUpdateRequest{RequestID: "req_upd", AgentID: "agt_000000000other"},
	})
	require.Error(t, err)
	require.Nil(t, approvals.submitRequest.Payload)
}

func TestAgentManagementDeleteCrossOwnerNoSubmit(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner", crossOwnerAgents: map[string]string{"agt_000000000cross": "usr_000000000other"}}
	approvals := &fakeApprovalRuntime{}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	_, err := handler.Delete(api.AgentScopedJSON[agentManagementDeleteRequest]{
		AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
		Body:               agentManagementDeleteRequest{RequestID: "req_del", AgentIDs: []string{"agt_000000000owned", "agt_000000000cross"}},
	})
	require.Error(t, err)
	require.Nil(t, approvals.submitRequest.Payload)
}

func TestAgentManagementPresentPendingErrorPropagates(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner"}
	approvals := &fakeApprovalRuntime{
		submitResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_1", State: approval.StatePending}},
		presentErr:   errors.New("projection failed"),
	}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	_, err := handler.Create(api.AgentScopedJSON[agentManagementCreateRequest]{
		AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
		Body:               agentManagementCreateRequest{RequestID: "req_1", Name: "n", MachineID: "mac", Runtime: "r"},
	})
	require.Error(t, err)
	require.Equal(t, 1, approvals.presentCount)
}

func TestAgentManagementSubmitErrorNoPresent(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner"}
	approvals := &fakeApprovalRuntime{submitErr: errors.New("submit failed")}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	_, err := handler.Create(api.AgentScopedJSON[agentManagementCreateRequest]{
		AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
		Body:               agentManagementCreateRequest{RequestID: "req_1", Name: "n", MachineID: "mac", Runtime: "r"},
	})
	require.Error(t, err)
	require.Equal(t, 0, approvals.presentCount)
}

func TestAgentManagementNonPendingSkipsPresentReturnsPending(t *testing.T) {
	owner := fakeManagementOwner{ownerUserID: "usr_000000000owner"}
	approvals := &fakeApprovalRuntime{
		replayFound:  true,
		replayResult: approvalapp.SubmitResult{Instance: approval.Instance{ID: "appr_1", State: approval.StateApproved}},
	}
	handler := &AgentManagementHandler{owners: owner, approvals: approvals}
	resp, err := handler.Create(api.AgentScopedJSON[agentManagementCreateRequest]{
		AgentScopedRequest: api.AgentScopedRequest{Gin: newAgentManagementTestGin(""), Principal: agent.AuthenticatedAgent{AgentID: "agt_00000000000app", WorkspaceID: "wsp", MemberID: "000000000app"}},
		Body:               agentManagementCreateRequest{RequestID: "req_1", Name: "n", MachineID: "mac", Runtime: "r"},
	})
	require.NoError(t, err)
	require.Equal(t, 1, approvals.replayCount)
	require.Equal(t, 0, approvals.submitCount)
	require.Equal(t, 0, approvals.presentCount)
	body, ok := resp.Body.(agentManagementResponse)
	require.True(t, ok)
	require.Equal(t, "pending", body.Status)
	require.Equal(t, "appr_1", body.ApprovalID)
}

type fakeLifecycleCommands struct {
	startedIDs []string
	stoppedIDs []string
	startErr   error
	stopErr    error
}

func (commands *fakeLifecycleCommands) StartAgentRuntimeForUser(_ context.Context, _, _, agentID string) (agent.AgentView, error) {
	commands.startedIDs = append(commands.startedIDs, agentID)
	return agent.AgentView{}, commands.startErr
}
func (commands *fakeLifecycleCommands) StopAgentRuntimeForUser(_ context.Context, _, _, agentID string) (agent.AgentView, error) {
	commands.stoppedIDs = append(commands.stoppedIDs, agentID)
	return agent.AgentView{}, commands.stopErr
}

type fakeRuntimeControl struct {
	connected  bool
	running    bool
	restartErr error
	restarts   int
}

func (control *fakeRuntimeControl) MachineConnected(string) bool            { return control.connected }
func (control *fakeRuntimeControl) AgentRuntimeRunning(string, string) bool { return control.running }
func (control *fakeRuntimeControl) RestartAgent(context.Context, string, string) error {
	control.restarts++
	return control.restartErr
}

type fakeReceiptRecord struct {
	state, resultStatus string
}

type fakeLifecycleReceipts struct {
	reserveReceipt  agentops.LifecycleReceipt
	reserveReplayed bool
	reserveErr      error
	markTransition  bool
	markErr         error
	recordOk        bool
	recordErr       error
	recorded        []fakeReceiptRecord
	reserveCalls    int
}

func (receipts *fakeLifecycleReceipts) ReserveLifecycleReceipt(context.Context, agentops.LifecycleReceiptIntent) (agentops.LifecycleReceipt, bool, error) {
	receipts.reserveCalls++
	return receipts.reserveReceipt, receipts.reserveReplayed, receipts.reserveErr
}
func (receipts *fakeLifecycleReceipts) MarkLifecycleDispatching(context.Context, string, string, string) (bool, error) {
	return receipts.markTransition, receipts.markErr
}
func (receipts *fakeLifecycleReceipts) RecordLifecycleResult(_ context.Context, _, _, _, _, state, resultStatus string) (bool, error) {
	receipts.recorded = append(receipts.recorded, fakeReceiptRecord{state: state, resultStatus: resultStatus})
	return receipts.recordOk, receipts.recordErr
}

func newManagementTestHandler(commands *fakeLifecycleCommands, control *fakeRuntimeControl, receipts *fakeLifecycleReceipts) *AgentManagementHandler {
	return &AgentManagementHandler{
		owners:          fakeManagementOwner{ownerUserID: "usr-owner", target: agent.AgentView{AgentID: "agt-target", MachineID: "mch-target", AutoWakeEnabled: true}},
		runtimeCommands: commands,
		runtimeControl:  control,
		receipts:        receipts,
	}
}

// newManagementTestHandlerWithAutoWake overrides the target's auto-wake flag so
// the two-dimensional (running, auto-wake) convergence can be exercised.
func newManagementTestHandlerWithAutoWake(autoWake bool, commands *fakeLifecycleCommands, control *fakeRuntimeControl, receipts *fakeLifecycleReceipts) *AgentManagementHandler {
	handler := newManagementTestHandler(commands, control, receipts)
	handler.owners = fakeManagementOwner{ownerUserID: "usr-owner", target: agent.AgentView{AgentID: "agt-target", MachineID: "mch-target", AutoWakeEnabled: autoWake}}
	return handler
}

func managementPrincipal() agent.AuthenticatedAgent {
	return agent.AuthenticatedAgent{AgentID: "agt-caller", WorkspaceID: "wsp-1", MachineID: "mch-caller"}
}

func preparedReceipt() agentops.LifecycleReceipt {
	return agentops.LifecycleReceipt{State: agentops.LifecycleReceiptStatePrepared}
}

// newReadyReceipts returns a receipt ledger faking a fresh prepared receipt that
// transitions through dispatch and records successfully.
func newReadyReceipts() *fakeLifecycleReceipts {
	return &fakeLifecycleReceipts{reserveReceipt: preparedReceipt(), markTransition: true, recordOk: true}
}

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

	commands := &fakeLifecycleCommands{}
	control := &fakeRuntimeControl{connected: true, running: false}
	receipts := newReadyReceipts()
	handler := newManagementTestHandler(commands, control, receipts)

	status, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStart, "req-1", "agt-target", "corr-1")

	require.NoError(t, err)
	require.Equal(t, agentManagementResultStarted, status)
	require.Equal(t, []string{"agt-target"}, commands.startedIDs)
	require.Equal(t, []fakeReceiptRecord{{agentops.LifecycleReceiptStateSucceeded, agentManagementResultStarted}}, receipts.recorded)
}

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

	commands := &fakeLifecycleCommands{}
	control := &fakeRuntimeControl{connected: true, running: true}
	receipts := newReadyReceipts()
	handler := newManagementTestHandler(commands, control, receipts)

	status, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStart, "req-1", "agt-target", "")

	require.NoError(t, err)
	require.Equal(t, agentManagementResultAlreadyStarted, status)
	require.Empty(t, commands.startedIDs, "already-running start does not re-dispatch")
}

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

	commands := &fakeLifecycleCommands{}
	control := &fakeRuntimeControl{connected: true, running: true}
	receipts := newReadyReceipts()
	handler := newManagementTestHandler(commands, control, receipts)

	status, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStop, "req-1", "agt-target", "")

	require.NoError(t, err)
	require.Equal(t, agentManagementResultStopping, status)
	require.Equal(t, []string{"agt-target"}, commands.stoppedIDs)
}

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

	commands := &fakeLifecycleCommands{}
	control := &fakeRuntimeControl{connected: true, running: false}
	receipts := newReadyReceipts()
	handler := newManagementTestHandlerWithAutoWake(false, commands, control, receipts)

	status, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStop, "req-1", "agt-target", "")

	require.NoError(t, err)
	require.Equal(t, agentManagementResultAlreadyStopped, status)
	require.Empty(t, commands.stoppedIDs)
}

// TestManagementStartStopDelegatesToLifecycleServiceExceptAtGoal asserts the
// handler short-circuits only on the full two-dimensional goal state and
// otherwise delegates exactly one Start/Stop call to the lifecycle service; the
// service owns the per-dimension no-duplicate-send convergence (covered by the
// service-level truth table).
func TestManagementStartStopDelegatesToLifecycleServiceExceptAtGoal(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name       string
		action     string
		running    bool
		autoWake   bool
		wantStatus string
		wantCalls  int
	}{
		{"start T,T already", agentops.LifecycleActionStart, true, true, agentManagementResultAlreadyStarted, 0},
		{"start T,F converge", agentops.LifecycleActionStart, true, false, agentManagementResultAlreadyStarted, 1},
		{"start F,T converge", agentops.LifecycleActionStart, false, true, agentManagementResultStarted, 1},
		{"start F,F converge", agentops.LifecycleActionStart, false, false, agentManagementResultStarted, 1},
		{"stop F,F already", agentops.LifecycleActionStop, false, false, agentManagementResultAlreadyStopped, 0},
		{"stop F,T converge", agentops.LifecycleActionStop, false, true, agentManagementResultAlreadyStopped, 1},
		{"stop T,F converge", agentops.LifecycleActionStop, true, false, agentManagementResultStopping, 1},
		{"stop T,T converge", agentops.LifecycleActionStop, true, true, agentManagementResultStopping, 1},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			commands := &fakeLifecycleCommands{}
			control := &fakeRuntimeControl{connected: true, running: tt.running}
			receipts := newReadyReceipts()
			handler := newManagementTestHandlerWithAutoWake(tt.autoWake, commands, control, receipts)

			status, err := handler.execute(context.Background(), managementPrincipal(), tt.action, "req-1", "agt-target", "")

			require.NoError(t, err)
			require.Equal(t, tt.wantStatus, status)
			require.Equal(t, tt.wantCalls, len(commands.startedIDs)+len(commands.stoppedIDs), "lifecycle service call count")
		})
	}
}

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

	commands := &fakeLifecycleCommands{}
	control := &fakeRuntimeControl{connected: true, running: true}
	receipts := newReadyReceipts()
	handler := newManagementTestHandler(commands, control, receipts)

	status, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionRestart, "req-1", "agt-target", "")

	require.NoError(t, err)
	require.Equal(t, agentManagementResultRestarted, status)
	require.Equal(t, 1, control.restarts)
}

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

	control := &fakeRuntimeControl{connected: true, running: false}
	receipts := newReadyReceipts()
	handler := newManagementTestHandler(&fakeLifecycleCommands{}, control, receipts)

	_, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionRestart, "req-1", "agt-target", "")

	require.ErrorIs(t, err, errAgentNotRunning, "restart of a stopped agent is a 409 agent_not_running")
	require.Equal(t, 0, control.restarts, "restart does not dispatch when not running")
}

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

	commands := &fakeLifecycleCommands{}
	control := &fakeRuntimeControl{connected: false, running: true}
	receipts := newReadyReceipts()
	handler := newManagementTestHandler(commands, control, receipts)

	_, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStart, "req-1", "agt-target", "")

	require.ErrorIs(t, err, errAgentMachineOffline, "offline is a distinct 409 machine_offline, never stopped/already_*")
	require.Empty(t, commands.startedIDs, "offline never dispatches")
	require.Empty(t, receipts.recorded, "offline short-circuits before a terminal record")
}

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

	commands := &fakeLifecycleCommands{}
	control := &fakeRuntimeControl{connected: false, running: false} // intentionally volatile: replay must win
	receipts := &fakeLifecycleReceipts{
		reserveReceipt:  agentops.LifecycleReceipt{State: agentops.LifecycleReceiptStateSucceeded, ResultStatus: agentManagementResultStopping},
		reserveReplayed: true,
	}
	handler := newManagementTestHandler(commands, control, receipts)

	status, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStop, "req-1", "agt-target", "")

	require.NoError(t, err)
	require.Equal(t, agentManagementResultStopping, status)
	require.Empty(t, commands.stoppedIDs, "replay does not re-dispatch")
	require.Empty(t, receipts.recorded, "replay does not record a new result")
}

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

	control := &fakeRuntimeControl{connected: true, running: true}
	receipts := &fakeLifecycleReceipts{
		reserveReceipt:  agentops.LifecycleReceipt{State: agentops.LifecycleReceiptStateIndeterminate},
		reserveReplayed: true,
	}
	handler := newManagementTestHandler(&fakeLifecycleCommands{}, control, receipts)

	_, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionRestart, "req-1", "agt-target", "")

	require.ErrorIs(t, err, errAgentRuntimeOutcomeUnknown, "an indeterminate replay surfaces a 409, never a success status")
	require.Equal(t, 0, control.restarts)
}

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

	control := &fakeRuntimeControl{connected: true, running: true}
	receipts := &fakeLifecycleReceipts{
		reserveReceipt: preparedReceipt(),
		markTransition: false, // a prior attempt or concurrent caller holds the dispatch
	}
	handler := newManagementTestHandler(&fakeLifecycleCommands{}, control, receipts)

	_, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionRestart, "req-1", "agt-target", "")

	require.ErrorIs(t, err, errAgentRequestInProgress, "a concurrent/crashed dispatch returns request_in_progress")
	require.Equal(t, 0, control.restarts, "never re-sent")
	require.Empty(t, receipts.recorded, "no terminal written")
}

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

	commands := &fakeLifecycleCommands{}
	control := &fakeRuntimeControl{connected: true, running: false}
	receipts := &fakeLifecycleReceipts{reserveReceipt: preparedReceipt(), markTransition: true, recordOk: false} // CAS miss
	handler := newManagementTestHandler(commands, control, receipts)

	_, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStart, "req-1", "agt-target", "")

	require.ErrorIs(t, err, errAgentRuntimeOutcomeUnknown, "a success that cannot be durably recorded is indeterminate, never silent success")
	require.Equal(t, []string{"agt-target"}, commands.startedIDs, "the command was dispatched")
}

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

	receipts := &fakeLifecycleReceipts{reserveErr: agentops.ErrLifecyclePayloadConflict}
	handler := newManagementTestHandler(&fakeLifecycleCommands{}, &fakeRuntimeControl{connected: true}, receipts)

	_, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStart, "req-1", "agt-target", "")

	require.ErrorIs(t, mapAgentLifecycleError(err), errAgentIdempotencyConflict)
}

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

	commands := &fakeLifecycleCommands{}
	control := &fakeRuntimeControl{connected: true, running: false}
	receipts := &fakeLifecycleReceipts{
		reserveReceipt:  preparedReceipt(), // a prior attempt reserved but never dispatched
		reserveReplayed: true,
		markTransition:  true,
		recordOk:        true,
	}
	handler := newManagementTestHandler(commands, control, receipts)

	status, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStart, "req-1", "agt-target", "")

	require.NoError(t, err)
	require.Equal(t, agentManagementResultStarted, status, "a prepared replay follows current runtime truth and dispatches rather than pinning a stale result")
	require.Equal(t, []string{"agt-target"}, commands.startedIDs)
}

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

	handler := newManagementTestHandler(&fakeLifecycleCommands{}, &fakeRuntimeControl{connected: true}, newReadyReceipts())

	_, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStart, strings.Repeat("a", 129), "agt-target", "")

	require.Error(t, err)
	resp, ok := agentManagementErrorClassifier(err)
	require.True(t, ok)
	require.Equal(t, http.StatusBadRequest, resp.Status)
}

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

	commands := &fakeLifecycleCommands{}
	control := &fakeRuntimeControl{connected: true, running: false}
	receipts := &fakeLifecycleReceipts{
		reserveReceipt:  agentops.LifecycleReceipt{State: agentops.LifecycleReceiptStateDispatching},
		reserveReplayed: true,
	}
	handler := newManagementTestHandler(commands, control, receipts)

	_, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStart, "req-1", "agt-target", "")

	require.ErrorIs(t, err, errAgentRequestInProgress, "a dispatching replay returns request_in_progress, never re-dispatches")
	require.Empty(t, commands.startedIDs)
}

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

	dispatchErr := errors.New("runtime upgrade required")
	commands := &fakeLifecycleCommands{startErr: dispatchErr}
	control := &fakeRuntimeControl{connected: true, running: false}
	receipts := newReadyReceipts()
	handler := newManagementTestHandler(commands, control, receipts)

	_, err := handler.execute(context.Background(), managementPrincipal(), agentops.LifecycleActionStart, "req-1", "agt-target", "")

	require.ErrorIs(t, err, dispatchErr, "the original dispatch error is returned to the caller")
	require.Len(t, receipts.recorded, 1)
	require.Equal(t, agentops.LifecycleReceiptStateIndeterminate, receipts.recorded[0].state, "the receipt is recorded indeterminate so a replay returns runtime_outcome_unknown")
}

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

	tests := []struct {
		name   string
		err    error
		status int
		code   string
	}{
		{"management machine offline", errAgentMachineOffline, http.StatusConflict, "machine_offline"},
		{"management agent not running", errAgentNotRunning, http.StatusConflict, "agent_not_running"},
		{"management idempotency conflict", errAgentIdempotencyConflict, http.StatusConflict, "idempotency_conflict"},
		{"management runtime unknown", errAgentRuntimeOutcomeUnknown, http.StatusConflict, "runtime_outcome_unknown"},
		{"management request in progress", errAgentRequestInProgress, http.StatusConflict, "request_in_progress"},
		{"store conflict", storeerr.Conflict("c"), http.StatusConflict, "conflict"},
		{"store bad request", storeerr.BadRequest("b"), http.StatusBadRequest, "bad_request"},
		{"store not found", storeerr.NotFound("n"), http.StatusNotFound, "not_found"},
		{"store forbidden", storeerr.Forbidden("f"), http.StatusForbidden, "forbidden"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			resp, ok := agentManagementErrorClassifier(tt.err)
			require.True(t, ok, "classifier must handle %s", tt.name)
			require.Equal(t, tt.status, resp.Status)
			require.Equal(t, tt.code, resp.Code)
		})
	}
}
