package manager

import (
	"context"
	"encoding/json"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/yaoapp/yao/agent/robot/cache"
	"github.com/yaoapp/yao/agent/robot/store"
	"github.com/yaoapp/yao/agent/robot/types"
	"github.com/yaoapp/yao/agent/testutils"
)

// mockExecutor is a minimal Executor for unit testing
type mockExecutor struct {
	resumeErr error
}

func (m *mockExecutor) ExecuteWithControl(ctx *types.Context, robot *types.Robot, trigger types.TriggerType, data interface{}, execID string, control types.ExecutionControl) (*types.Execution, error) {
	return nil, fmt.Errorf("not implemented")
}
func (m *mockExecutor) ExecuteWithID(ctx *types.Context, robot *types.Robot, trigger types.TriggerType, data interface{}, execID string) (*types.Execution, error) {
	return nil, fmt.Errorf("not implemented")
}
func (m *mockExecutor) Execute(ctx *types.Context, robot *types.Robot, trigger types.TriggerType, data interface{}) (*types.Execution, error) {
	return nil, fmt.Errorf("not implemented")
}
func (m *mockExecutor) Resume(ctx *types.Context, execID string, reply string) error {
	return m.resumeErr
}
func (m *mockExecutor) ExecCount() int    { return 0 }
func (m *mockExecutor) CurrentCount() int { return 0 }
func (m *mockExecutor) Reset()            {}

// HL1: createConfirmingExecution
func TestCreateConfirmingExecution(t *testing.T) {
	m := &Manager{}

	t.Run("creates record with correct fields", func(t *testing.T) {
		if testing.Short() {
			t.Skip("Requires database")
		}
		testutils.Prepare(t)
		defer testutils.Clean(t)
		ctx := types.NewContext(context.Background(), nil)
		robot := &types.Robot{MemberID: "member-hl1", TeamID: "team-hl1"}
		req := &InteractRequest{Message: "do something"}
		execStore := store.NewExecutionStore()

		record, chatID, err := m.createConfirmingExecution(ctx, robot, req, execStore)
		require.NoError(t, err)
		assert.NotEmpty(t, record.ExecutionID)
		assert.Equal(t, "member-hl1", record.MemberID)
		assert.Equal(t, "team-hl1", record.TeamID)
		assert.Equal(t, types.ExecConfirming, record.Status)
		assert.Equal(t, types.TriggerHuman, record.TriggerType)
		assert.Equal(t, types.PhaseGoals, record.Phase)
		assert.Contains(t, chatID, "robot_member-hl1_")
		assert.Equal(t, chatID, record.ChatID)
		assert.NotNil(t, record.Input)
		assert.Equal(t, types.ActionTaskAdd, record.Input.Action)
		assert.Len(t, record.Input.Messages, 1)
		assert.Equal(t, "do something", record.Input.Messages[0].Content)
		assert.NotNil(t, record.StartTime)
	})

	t.Run("UserID empty when auth is nil", func(t *testing.T) {
		if testing.Short() {
			t.Skip("Requires database")
		}
		testutils.Prepare(t)
		defer testutils.Clean(t)
		ctx := types.NewContext(context.Background(), nil)
		robot := &types.Robot{MemberID: "member-hl1b", TeamID: "team-hl1b"}
		req := &InteractRequest{Message: "test"}
		execStore := store.NewExecutionStore()

		record, _, err := m.createConfirmingExecution(ctx, robot, req, execStore)
		require.NoError(t, err)
		assert.Empty(t, record.Input.UserID)
	})
}

// HL2-HL4: adjustExecution
func TestAdjustExecution(t *testing.T) {
	m := &Manager{}

	t.Run("adjusts goals from string", func(t *testing.T) {
		if testing.Short() {
			t.Skip("Requires database")
		}
		testutils.Prepare(t)
		defer testutils.Clean(t)
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{
			ExecutionID: "exec-hl2",
			MemberID:    "member-hl2",
			TriggerType: types.TriggerHuman,
			Status:      types.ExecPending,
			Phase:       types.PhaseInspiration,
		}
		execStore := store.NewExecutionStore()
		require.NoError(t, execStore.Save(ctx.Context, record))

		actionData := map[string]interface{}{"goals": "updated goals content"}
		err := m.adjustExecution(ctx, record, actionData, execStore)
		require.NoError(t, err)
		require.NotNil(t, record.Goals)
		assert.Equal(t, "updated goals content", record.Goals.Content)
	})

	t.Run("adjusts tasks from array", func(t *testing.T) {
		if testing.Short() {
			t.Skip("Requires database")
		}
		testutils.Prepare(t)
		defer testutils.Clean(t)
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{
			ExecutionID: "exec-hl3",
			MemberID:    "member-hl3",
			TriggerType: types.TriggerHuman,
			Status:      types.ExecPending,
			Phase:       types.PhaseInspiration,
		}
		execStore := store.NewExecutionStore()
		require.NoError(t, execStore.Save(ctx.Context, record))

		tasks := []map[string]interface{}{
			{"id": "t1", "name": "Task 1"},
			{"id": "t2", "name": "Task 2"},
		}
		actionData := map[string]interface{}{"tasks": tasks}
		err := m.adjustExecution(ctx, record, actionData, execStore)
		require.NoError(t, err)
		assert.Len(t, record.Tasks, 2)
	})

	t.Run("nil action data is noop", func(t *testing.T) {
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{}
		execStore := store.NewExecutionStore()

		err := m.adjustExecution(ctx, record, nil, execStore)
		require.NoError(t, err)
		assert.Nil(t, record.Goals)
	})

	t.Run("non-map action data handled gracefully", func(t *testing.T) {
		if testing.Short() {
			t.Skip("Requires database")
		}
		testutils.Prepare(t)
		defer testutils.Clean(t)
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{
			ExecutionID: "exec-hl4",
			MemberID:    "member-hl4",
			TriggerType: types.TriggerHuman,
			Status:      types.ExecPending,
			Phase:       types.PhaseInspiration,
		}
		execStore := store.NewExecutionStore()
		require.NoError(t, execStore.Save(ctx.Context, record))

		err := m.adjustExecution(ctx, record, "not a map", execStore)
		require.NoError(t, err)
	})
}

// HL5-HL6: injectTask
func TestInjectTask(t *testing.T) {
	m := &Manager{}

	t.Run("appends new task with auto-generated ID", func(t *testing.T) {
		if testing.Short() {
			t.Skip("Requires database")
		}
		testutils.Prepare(t)
		defer testutils.Clean(t)
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{
			ExecutionID: "exec-hl5",
			MemberID:    "member-hl5",
			TriggerType: types.TriggerHuman,
			Status:      types.ExecPending,
			Phase:       types.PhaseInspiration,
		}
		execStore := store.NewExecutionStore()
		require.NoError(t, execStore.Save(ctx.Context, record))

		taskData := map[string]interface{}{"name": "New Task"}
		err := m.injectTask(ctx, record, taskData, execStore)
		require.NoError(t, err)
		require.Len(t, record.Tasks, 1)
		assert.Contains(t, record.Tasks[0].ID, "injected-")
		assert.Equal(t, types.TaskPending, record.Tasks[0].Status)
	})

	t.Run("preserves existing tasks", func(t *testing.T) {
		if testing.Short() {
			t.Skip("Requires database")
		}
		testutils.Prepare(t)
		defer testutils.Clean(t)
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{
			ExecutionID: "exec-hl6",
			MemberID:    "member-hl6",
			TriggerType: types.TriggerHuman,
			Status:      types.ExecPending,
			Phase:       types.PhaseInspiration,
			Tasks: []types.Task{
				{ID: "existing-1", Description: "Existing"},
			},
		}
		execStore := store.NewExecutionStore()
		require.NoError(t, execStore.Save(ctx.Context, record))

		taskData := map[string]interface{}{"name": "Added Task"}
		err := m.injectTask(ctx, record, taskData, execStore)
		require.NoError(t, err)
		assert.Len(t, record.Tasks, 2)
		assert.Equal(t, "existing-1", record.Tasks[0].ID)
	})

	t.Run("nil action data returns error", func(t *testing.T) {
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{}
		execStore := store.NewExecutionStore()

		err := m.injectTask(ctx, record, nil, execStore)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "task data is required")
	})

	t.Run("respects provided task ID", func(t *testing.T) {
		if testing.Short() {
			t.Skip("Requires database")
		}
		testutils.Prepare(t)
		defer testutils.Clean(t)
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{
			ExecutionID: "exec-hl6b",
			MemberID:    "member-hl6b",
			TriggerType: types.TriggerHuman,
			Status:      types.ExecPending,
			Phase:       types.PhaseInspiration,
		}
		execStore := store.NewExecutionStore()
		require.NoError(t, execStore.Save(ctx.Context, record))

		taskData := map[string]interface{}{"id": "custom-id", "name": "Custom"}
		err := m.injectTask(ctx, record, taskData, execStore)
		require.NoError(t, err)
		assert.Equal(t, "custom-id", record.Tasks[0].ID)
	})
}

// HL7: callHostAgentForScenario
func TestCallHostAgentForScenario(t *testing.T) {
	m := &Manager{}

	t.Run("no host agent returns error", func(t *testing.T) {
		ctx := types.NewContext(context.Background(), nil)
		robot := &types.Robot{MemberID: "member-hl7"}

		_, err := m.callHostAgentForScenario(ctx, robot, "assign", "test", nil, "chat-1")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "no Host Agent configured")
	})

	t.Run("robot with nil config returns error", func(t *testing.T) {
		ctx := types.NewContext(context.Background(), nil)
		robot := &types.Robot{MemberID: "member-hl7b", Config: nil}

		_, err := m.callHostAgentForScenario(ctx, robot, "assign", "test", nil, "chat-1")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "no Host Agent configured")
	})
}

// HL8: directAssign (needs pool — tested in processHostAction)
// HL9-HL10: directResume (needs executor — tested in processHostAction)

// Updated buildRobotStatusSnapshot tests
func TestBuildRobotStatusSnapshotV2(t *testing.T) {
	m := &Manager{}

	t.Run("nil robot returns nil", func(t *testing.T) {
		snap := m.buildRobotStatusSnapshot(nil)
		assert.Nil(t, snap)
	})

	t.Run("populates MemberID and Status", func(t *testing.T) {
		robot := &types.Robot{
			MemberID: "member-snap",
			Status:   types.RobotWorking,
		}
		snap := m.buildRobotStatusSnapshot(robot)
		require.NotNil(t, snap)
		assert.Equal(t, "member-snap", snap.MemberID)
		assert.Equal(t, types.RobotWorking, snap.Status)
	})

	t.Run("uses ActiveCount and WaitingCount", func(t *testing.T) {
		robot := &types.Robot{MemberID: "member-snap2"}
		exec1 := &types.Execution{ID: "e1", Status: types.ExecRunning}
		exec2 := &types.Execution{ID: "e2", Status: types.ExecWaiting}
		robot.AddExecution(exec1)
		robot.AddExecution(exec2)

		snap := m.buildRobotStatusSnapshot(robot)
		require.NotNil(t, snap)
		assert.Equal(t, 1, snap.ActiveCount)
		assert.Equal(t, 1, snap.WaitingCount)
	})

	t.Run("populates ActiveExecs briefs", func(t *testing.T) {
		robot := &types.Robot{MemberID: "member-snap3"}
		exec := &types.Execution{ID: "e-brief", Status: types.ExecRunning, Name: "Test Exec"}
		robot.AddExecution(exec)

		snap := m.buildRobotStatusSnapshot(robot)
		require.NotNil(t, snap)
		require.Len(t, snap.ActiveExecs, 1)
		assert.Equal(t, "e-brief", snap.ActiveExecs[0].ID)
	})

	t.Run("uses robot MaxQuota", func(t *testing.T) {
		robot := &types.Robot{
			MemberID: "member-snap4",
			Config:   &types.Config{Quota: &types.Quota{Max: 7}},
		}
		snap := m.buildRobotStatusSnapshot(robot)
		require.NotNil(t, snap)
		assert.Equal(t, 7, snap.MaxQuota)
	})
}

// Test processHostAction — adjust branch
func TestProcessHostActionAdjust(t *testing.T) {
	if testing.Short() {
		t.Skip("Requires database")
	}
	testutils.Prepare(t)
	defer testutils.Clean(t)
	m := &Manager{}
	ctx := types.NewContext(context.Background(), nil)
	robot := &types.Robot{MemberID: "member-pa-adj"}

	t.Run("adjust with goals", func(t *testing.T) {
		record := &store.ExecutionRecord{
			ExecutionID: "exec-pa2",
			MemberID:    "member-pa-adj",
			TriggerType: types.TriggerHuman,
			Status:      types.ExecConfirming,
			Phase:       types.PhaseInspiration,
		}
		execStore := store.NewExecutionStore()
		require.NoError(t, execStore.Save(ctx.Context, record))

		output := &types.HostOutput{
			Reply:      "Plan adjusted",
			Action:     types.HostActionAdjust,
			ActionData: map[string]interface{}{"goals": "new goals"},
		}

		resp, err := m.processHostAction(ctx, robot, record, output, execStore)
		require.NoError(t, err)
		assert.Equal(t, "adjusted", resp.Status)
		require.NotNil(t, record.Goals)
		assert.Equal(t, "new goals", record.Goals.Content)
	})

	t.Run("adjust with tasks", func(t *testing.T) {
		record := &store.ExecutionRecord{
			ExecutionID: "exec-pa3",
			MemberID:    "member-pa-adj",
			TriggerType: types.TriggerHuman,
			Status:      types.ExecConfirming,
			Phase:       types.PhaseInspiration,
		}
		execStore := store.NewExecutionStore()
		require.NoError(t, execStore.Save(ctx.Context, record))

		tasksJSON := []map[string]interface{}{{"id": "t1", "name": "Adjusted Task"}}
		output := &types.HostOutput{
			Reply:      "Tasks updated",
			Action:     types.HostActionAdjust,
			ActionData: map[string]interface{}{"tasks": tasksJSON},
		}

		resp, err := m.processHostAction(ctx, robot, record, output, execStore)
		require.NoError(t, err)
		assert.Equal(t, "adjusted", resp.Status)
		assert.Len(t, record.Tasks, 1)
	})

	t.Run("adjust with nil data is noop", func(t *testing.T) {
		record := &store.ExecutionRecord{
			ExecutionID: "exec-pa4",
			MemberID:    "member-pa-adj",
			TriggerType: types.TriggerHuman,
			Status:      types.ExecConfirming,
			Phase:       types.PhaseInspiration,
		}
		execStore := store.NewExecutionStore()
		require.NoError(t, execStore.Save(ctx.Context, record))

		output := &types.HostOutput{
			Reply:  "No changes",
			Action: types.HostActionAdjust,
		}

		resp, err := m.processHostAction(ctx, robot, record, output, execStore)
		require.NoError(t, err)
		assert.Equal(t, "adjusted", resp.Status)
	})
}

// Test processHostAction — add_task branch
func TestProcessHostActionAddTask(t *testing.T) {
	if testing.Short() {
		t.Skip("Requires database")
	}
	testutils.Prepare(t)
	defer testutils.Clean(t)
	m := &Manager{}
	ctx := types.NewContext(context.Background(), nil)
	robot := &types.Robot{MemberID: "member-pa-at"}

	t.Run("add task success", func(t *testing.T) {
		record := &store.ExecutionRecord{
			ExecutionID: "exec-pa5",
			MemberID:    "member-pa-at",
			TriggerType: types.TriggerHuman,
			Status:      types.ExecConfirming,
			Phase:       types.PhaseInspiration,
		}
		execStore := store.NewExecutionStore()
		require.NoError(t, execStore.Save(ctx.Context, record))

		output := &types.HostOutput{
			Reply:      "Task added",
			Action:     types.HostActionAddTask,
			ActionData: map[string]interface{}{"name": "New task"},
		}

		resp, err := m.processHostAction(ctx, robot, record, output, execStore)
		require.NoError(t, err)
		assert.Equal(t, "task_added", resp.Status)
		assert.Len(t, record.Tasks, 1)
	})

	t.Run("add task nil data returns error", func(t *testing.T) {
		record := &store.ExecutionRecord{
			ExecutionID: "exec-pa6",
			MemberID:    "member-pa-at",
		}
		execStore := store.NewExecutionStore()

		output := &types.HostOutput{
			Reply:  "Add task",
			Action: types.HostActionAddTask,
		}

		_, err := m.processHostAction(ctx, robot, record, output, execStore)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "task data is required")
	})
}

// Test processHostAction — skip branch
func TestProcessHostActionSkip(t *testing.T) {
	m := &Manager{}
	ctx := types.NewContext(context.Background(), nil)
	robot := &types.Robot{MemberID: "member-pa-skip"}

	t.Run("skip without waiting task returns error", func(t *testing.T) {
		record := &store.ExecutionRecord{
			ExecutionID: "exec-pa8",
			MemberID:    "member-pa-skip",
		}
		execStore := store.NewExecutionStore()

		output := &types.HostOutput{
			Reply:  "Skip it",
			Action: types.HostActionSkip,
		}

		_, err := m.processHostAction(ctx, robot, record, output, execStore)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "no task is waiting")
	})
}

// Test processHostAction — wait_for_more and default
func TestProcessHostActionWaitForMoreAndDefault(t *testing.T) {
	m := &Manager{}
	ctx := types.NewContext(context.Background(), nil)
	robot := &types.Robot{MemberID: "member-pa-wfm"}

	t.Run("wait_for_more", func(t *testing.T) {
		record := &store.ExecutionRecord{}
		execStore := store.NewExecutionStore()

		output := &types.HostOutput{
			Reply:       "More details please",
			WaitForMore: true,
		}

		resp, err := m.processHostAction(ctx, robot, record, output, execStore)
		require.NoError(t, err)
		assert.Equal(t, "waiting_for_more", resp.Status)
		assert.Equal(t, "More details please", resp.Reply)
		assert.True(t, resp.WaitForMore)
	})

	t.Run("unknown action returns acknowledged", func(t *testing.T) {
		record := &store.ExecutionRecord{}
		execStore := store.NewExecutionStore()

		output := &types.HostOutput{
			Reply:  "OK",
			Action: "unknown_action",
		}

		resp, err := m.processHostAction(ctx, robot, record, output, execStore)
		require.NoError(t, err)
		assert.Equal(t, "acknowledged", resp.Status)
		assert.Equal(t, "OK", resp.Message)
	})
}

// Test processHostAction — cancel branch
func TestProcessHostActionCancel(t *testing.T) {
	if testing.Short() {
		t.Skip("Requires database")
	}
	testutils.Prepare(t)
	defer testutils.Clean(t)

	t.Run("cancel waiting execution", func(t *testing.T) {
		// Cannot fully test without a started manager; verify the error path
		m := &Manager{started: false}
		ctx := types.NewContext(context.Background(), nil)
		robot := &types.Robot{MemberID: "member-pa-cancel"}

		record := &store.ExecutionRecord{
			ExecutionID: "exec-pa11",
			MemberID:    "member-pa-cancel",
		}
		execStore := store.NewExecutionStore()

		output := &types.HostOutput{
			Reply:  "Cancel it",
			Action: types.HostActionCancel,
		}

		_, err := m.processHostAction(ctx, robot, record, output, execStore)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "manager not started")
	})
}

// Test HandleInteract validation
func TestHandleInteractValidationExtended(t *testing.T) {
	t.Run("manager not started returns error", func(t *testing.T) {
		m := &Manager{started: false}
		_, err := m.HandleInteract(types.NewContext(context.Background(), nil), "member-1", &InteractRequest{Message: "test"})
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "manager not started")
	})

	t.Run("empty member_id returns error", func(t *testing.T) {
		m := &Manager{started: true}
		_, err := m.HandleInteract(types.NewContext(context.Background(), nil), "", &InteractRequest{Message: "test"})
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "member_id is required")
	})

	t.Run("nil request returns error", func(t *testing.T) {
		m := &Manager{started: true}
		_, err := m.HandleInteract(types.NewContext(context.Background(), nil), "member-1", nil)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "message is required")
	})

	t.Run("empty message returns error", func(t *testing.T) {
		m := &Manager{started: true}
		_, err := m.HandleInteract(types.NewContext(context.Background(), nil), "member-1", &InteractRequest{})
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "message is required")
	})

	t.Run("non-interactable status returns error", func(t *testing.T) {
		if testing.Short() {
			t.Skip("Requires database and cache")
		}
		testutils.Prepare(t)
		defer testutils.Clean(t)
		// Would require a full Manager with cache — tested via E2E
	})
}

// Test CancelExecution validation
func TestCancelExecutionValidationExtended(t *testing.T) {
	t.Run("manager not started", func(t *testing.T) {
		m := &Manager{started: false}
		err := m.CancelExecution(types.NewContext(context.Background(), nil), "exec-1")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "manager not started")
	})
}

// Test buildHostContext JSON output
func TestBuildHostContextJSON(t *testing.T) {
	m := &Manager{}

	robot := &types.Robot{MemberID: "member-ctx"}
	record := &store.ExecutionRecord{
		Goals:           &types.Goals{Content: "test goals"},
		Tasks:           []types.Task{{ID: "t1"}},
		WaitingQuestion: "What time?",
	}
	waitingTask := &types.Task{ID: "t1", Status: types.TaskWaitingInput}

	hostCtx := m.buildHostContext(robot, record, waitingTask)
	require.NotNil(t, hostCtx)

	data, err := json.Marshal(hostCtx)
	require.NoError(t, err)

	var parsed map[string]interface{}
	err = json.Unmarshal(data, &parsed)
	require.NoError(t, err)

	// Goals is a struct, not a plain string
	goalsRaw, ok := parsed["goals"]
	require.True(t, ok)
	goalsMap, ok := goalsRaw.(map[string]interface{})
	require.True(t, ok, "Goals should be a JSON object, not a string")
	assert.Equal(t, "test goals", goalsMap["content"])

	assert.Equal(t, "What time?", parsed["agent_reply"])
}

// ==================== processHostAction -- confirm branch (PA1) ====================

func TestProcessHostActionConfirmRequiresPool(t *testing.T) {
	if testing.Short() {
		t.Skip("Requires database")
	}
	testutils.Prepare(t)
	defer testutils.Clean(t)
	m := &Manager{started: false}
	ctx := types.NewContext(context.Background(), nil)
	robot := &types.Robot{MemberID: "member-pa1"}
	record := &store.ExecutionRecord{
		ExecutionID: "exec-pa1",
		MemberID:    "member-pa1",
		Status:      types.ExecConfirming,
	}
	execStore := store.NewExecutionStore()

	output := &types.HostOutput{
		Reply:  "Confirmed",
		Action: types.HostActionConfirm,
	}

	assert.Panics(t, func() {
		m.processHostAction(ctx, robot, record, output, execStore)
	}, "should panic because pool/executor are nil")
}

// ==================== processHostAction -- inject_ctx branch (PA9-PA10) ====================

func TestProcessHostActionInjectCtx(t *testing.T) {
	t.Run("nil executor panics", func(t *testing.T) {
		m := &Manager{}
		ctx := types.NewContext(context.Background(), nil)
		robot := &types.Robot{MemberID: "member-pa9"}
		record := &store.ExecutionRecord{
			ExecutionID: "exec-pa9",
			MemberID:    "member-pa9",
			Status:      types.ExecWaiting,
		}
		execStore := store.NewExecutionStore()

		output := &types.HostOutput{
			Reply:      "Here's context",
			Action:     types.HostActionInjectCtx,
			ActionData: "additional context data",
		}

		assert.Panics(t, func() {
			m.processHostAction(ctx, robot, record, output, execStore)
		})
	})

	t.Run("with mock executor delegates resume", func(t *testing.T) {
		mockExec := &mockExecutor{resumeErr: fmt.Errorf("mock error")}
		m := &Manager{executor: mockExec}
		ctx := types.NewContext(context.Background(), nil)
		robot := &types.Robot{MemberID: "member-pa10"}
		record := &store.ExecutionRecord{
			ExecutionID: "exec-pa10",
			MemberID:    "member-pa10",
		}
		execStore := store.NewExecutionStore()

		output := &types.HostOutput{
			Reply:      "Resume with data",
			Action:     types.HostActionInjectCtx,
			ActionData: map[string]interface{}{"reply": "detailed info"},
		}

		_, err := m.processHostAction(ctx, robot, record, output, execStore)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "mock error")
	})

	t.Run("ErrExecutionSuspended returns waiting status", func(t *testing.T) {
		mockExec := &mockExecutor{resumeErr: types.ErrExecutionSuspended}
		m := &Manager{executor: mockExec}
		ctx := types.NewContext(context.Background(), nil)
		robot := &types.Robot{MemberID: "member-pa10b"}
		record := &store.ExecutionRecord{
			ExecutionID: "exec-pa10b",
			MemberID:    "member-pa10b",
		}
		execStore := store.NewExecutionStore()

		output := &types.HostOutput{
			Reply:      "Resume",
			Action:     types.HostActionInjectCtx,
			ActionData: "context",
		}

		resp, err := m.processHostAction(ctx, robot, record, output, execStore)
		require.NoError(t, err)
		assert.Equal(t, "waiting", resp.Status)
	})
}

// ==================== HandleInteract routing (HI5-HI8) ====================

func TestHandleInteractRouting(t *testing.T) {
	t.Run("HI5: non-existent execution_id returns error", func(t *testing.T) {
		if testing.Short() {
			t.Skip("Requires database and cache")
		}
		testutils.Prepare(t)
		defer testutils.Clean(t)

		m := &Manager{started: true, cache: cache.New()}
		ctx := types.NewContext(context.Background(), nil)
		_, err := m.HandleInteract(ctx, "member-hi5", &InteractRequest{
			ExecutionID: "nonexistent-exec",
			Message:     "test",
		})
		assert.Error(t, err)
	})

	t.Run("HI6: non-existent robot returns error", func(t *testing.T) {
		if testing.Short() {
			t.Skip("Requires database and cache")
		}
		testutils.Prepare(t)
		defer testutils.Clean(t)
		m := &Manager{started: true, cache: cache.New()}
		ctx := types.NewContext(context.Background(), nil)
		_, err := m.HandleInteract(ctx, "nonexistent-robot", &InteractRequest{
			Message: "test",
		})
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "robot not found")
	})
}

// ==================== CancelExecution validation (CE2-CE5) ====================

func TestCancelExecutionStatusValidation(t *testing.T) {
	if testing.Short() {
		t.Skip("Requires database")
	}
	testutils.Prepare(t)
	defer testutils.Clean(t)

	t.Run("CE2: non-existent execution returns error", func(t *testing.T) {
		m := &Manager{started: true}
		ctx := types.NewContext(context.Background(), nil)
		err := m.CancelExecution(ctx, "nonexistent-exec")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "execution not found")
	})

	t.Run("CE3: running execution cannot be cancelled", func(t *testing.T) {
		m := &Manager{started: true}
		ctx := types.NewContext(context.Background(), nil)

		execStore := store.NewExecutionStore()
		record := &store.ExecutionRecord{
			ExecutionID: "exec-ce3",
			MemberID:    "member-ce3",
			Status:      types.ExecRunning,
			TriggerType: types.TriggerHuman,
			Phase:       types.PhaseInspiration,
		}
		require.NoError(t, execStore.Save(ctx.Context, record))

		err := m.CancelExecution(ctx, "exec-ce3")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "only waiting/confirming can be cancelled")
	})

	t.Run("CE4: completed execution cannot be cancelled", func(t *testing.T) {
		m := &Manager{started: true}
		ctx := types.NewContext(context.Background(), nil)

		execStore := store.NewExecutionStore()
		record := &store.ExecutionRecord{
			ExecutionID: "exec-ce4",
			MemberID:    "member-ce4",
			Status:      types.ExecCompleted,
			TriggerType: types.TriggerHuman,
			Phase:       types.PhaseInspiration,
		}
		require.NoError(t, execStore.Save(ctx.Context, record))

		err := m.CancelExecution(ctx, "exec-ce4")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "only waiting/confirming can be cancelled")
	})
}

// ==================== InteractRequest/InteractResponse struct validation ====================

func TestInteractRequestStructFields(t *testing.T) {
	req := &InteractRequest{
		ExecutionID: "exec-1",
		TaskID:      "task-1",
		Source:      types.InteractSourceUI,
		Message:     "do something",
		Action:      "confirm",
	}
	assert.Equal(t, "exec-1", req.ExecutionID)
	assert.Equal(t, "task-1", req.TaskID)
	assert.Equal(t, types.InteractSourceUI, req.Source)
	assert.Equal(t, "do something", req.Message)
	assert.Equal(t, "confirm", req.Action)
}

func TestInteractResponseStructFields(t *testing.T) {
	resp := &InteractResponse{
		ExecutionID: "exec-1",
		Status:      "confirmed",
		Message:     "Done",
		ChatID:      "chat-1",
		Reply:       "I'll do it",
		WaitForMore: true,
	}
	assert.Equal(t, "exec-1", resp.ExecutionID)
	assert.Equal(t, "confirmed", resp.Status)
	assert.Equal(t, "Done", resp.Message)
	assert.Equal(t, "chat-1", resp.ChatID)
	assert.Equal(t, "I'll do it", resp.Reply)
	assert.True(t, resp.WaitForMore)
}

// ==================== executeResume helper ====================

func TestExecuteResumeNilExecutor(t *testing.T) {
	m := &Manager{}
	ctx := types.NewContext(context.Background(), nil)

	assert.Panics(t, func() {
		_ = m.executeResume(ctx, "exec-test", "reply")
	})
}

func TestExecuteResumeWithMock(t *testing.T) {
	t.Run("delegates to executor Resume", func(t *testing.T) {
		mockExec := &mockExecutor{resumeErr: nil}
		m := &Manager{executor: mockExec}
		ctx := types.NewContext(context.Background(), nil)

		err := m.executeResume(ctx, "exec-test", "reply")
		assert.NoError(t, err)
	})

	t.Run("propagates error", func(t *testing.T) {
		mockExec := &mockExecutor{resumeErr: fmt.Errorf("resume failed")}
		m := &Manager{executor: mockExec}
		ctx := types.NewContext(context.Background(), nil)

		err := m.executeResume(ctx, "exec-test", "reply")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "resume failed")
	})

	t.Run("propagates ErrExecutionSuspended", func(t *testing.T) {
		mockExec := &mockExecutor{resumeErr: types.ErrExecutionSuspended}
		m := &Manager{executor: mockExec}
		ctx := types.NewContext(context.Background(), nil)

		err := m.executeResume(ctx, "exec-test", "reply")
		assert.Equal(t, types.ErrExecutionSuspended, err)
	})
}

// ==================== skipWaitingTask and directResume with mock ====================

func TestSkipWaitingTaskWithMock(t *testing.T) {
	t.Run("no waiting task returns error", func(t *testing.T) {
		mockExec := &mockExecutor{}
		m := &Manager{executor: mockExec}
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{
			ExecutionID: "exec-skip",
		}
		execStore := store.NewExecutionStore()

		err := m.skipWaitingTask(ctx, record, execStore)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "no task is waiting")
	})

	t.Run("marks waiting task as skipped and resumes", func(t *testing.T) {
		mockExec := &mockExecutor{resumeErr: nil}
		m := &Manager{executor: mockExec}
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{
			ExecutionID:   "exec-skip2",
			WaitingTaskID: "task-w",
			Tasks: []types.Task{
				{ID: "task-w", Status: types.TaskWaitingInput},
			},
		}
		execStore := store.NewExecutionStore()

		err := m.skipWaitingTask(ctx, record, execStore)
		assert.NoError(t, err)
		assert.Equal(t, types.TaskSkipped, record.Tasks[0].Status)
	})
}

func TestDirectResumeWithMock(t *testing.T) {
	t.Run("successful resume", func(t *testing.T) {
		mockExec := &mockExecutor{resumeErr: nil}
		m := &Manager{executor: mockExec}
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{
			ExecutionID: "exec-dr",
			ChatID:      "chat-dr",
		}
		req := &InteractRequest{Message: "continue"}

		resp, err := m.directResume(ctx, record, req)
		require.NoError(t, err)
		assert.Equal(t, "resumed", resp.Status)
		assert.Equal(t, "exec-dr", resp.ExecutionID)
		assert.Equal(t, "chat-dr", resp.ChatID)
	})

	t.Run("suspended again", func(t *testing.T) {
		mockExec := &mockExecutor{resumeErr: types.ErrExecutionSuspended}
		m := &Manager{executor: mockExec}
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{
			ExecutionID: "exec-dr2",
			ChatID:      "chat-dr2",
		}
		req := &InteractRequest{Message: "continue"}

		resp, err := m.directResume(ctx, record, req)
		require.NoError(t, err)
		assert.Equal(t, "waiting", resp.Status)
	})

	t.Run("error propagated", func(t *testing.T) {
		mockExec := &mockExecutor{resumeErr: fmt.Errorf("resume failed")}
		m := &Manager{executor: mockExec}
		ctx := types.NewContext(context.Background(), nil)
		record := &store.ExecutionRecord{
			ExecutionID: "exec-dr3",
		}
		req := &InteractRequest{Message: "continue"}

		_, err := m.directResume(ctx, record, req)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "resume failed")
	})
}
