package standard_test

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	agentcontext "github.com/yaoapp/yao/agent/context"
	"github.com/yaoapp/yao/agent/robot/executor/standard"
	executortypes "github.com/yaoapp/yao/agent/robot/executor/types"
	"github.com/yaoapp/yao/agent/robot/types"
	"github.com/yaoapp/yao/agent/testutils"
)

// ============================================================================
// RunExecution with ResumeContext tests
// ============================================================================

func TestRunExecutionResumeContext(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test")
	}

	testutils.Prepare(t)
	defer testutils.Clean(t)

	ctx := types.NewContext(context.Background(), testAuth())

	t.Run("resumes from task index with previous results", func(t *testing.T) {
		robot := createRunTestRobot(t)
		exec := createRunTestExecution(robot)

		exec.Tasks = []types.Task{
			{
				ID:           "task-001",
				ExecutorType: types.ExecutorAssistant,
				ExecutorID:   "experts.text-writer",
				Messages: []agentcontext.Message{
					{Role: agentcontext.RoleUser, Content: "Write 'hello'"},
				},
				Order:  0,
				Status: types.TaskCompleted,
			},
			{
				ID:           "task-002",
				ExecutorType: types.ExecutorAssistant,
				ExecutorID:   "experts.text-writer",
				Messages: []agentcontext.Message{
					{Role: agentcontext.RoleUser, Content: "Write 'world'"},
				},
				Order:  1,
				Status: types.TaskPending,
			},
			{
				ID:           "task-003",
				ExecutorType: types.ExecutorAssistant,
				ExecutorID:   "experts.text-writer",
				Messages: []agentcontext.Message{
					{Role: agentcontext.RoleUser, Content: "Write '!'"},
				},
				Order:  2,
				Status: types.TaskPending,
			},
		}

		// Simulate resume: task-001 already completed, resume from task-002
		previousResult := types.TaskResult{
			TaskID:   "task-001",
			Success:  true,
			Output:   "hello",
			Duration: 100,
		}
		exec.ResumeContext = &types.ResumeContext{
			TaskIndex:       1,
			PreviousResults: []types.TaskResult{previousResult},
		}

		e := standard.New()
		err := e.RunExecution(ctx, exec, nil)

		require.NoError(t, err)
		// Should have 3 results: 1 from previous + 2 new
		require.Len(t, exec.Results, 3)

		assert.Equal(t, "task-001", exec.Results[0].TaskID)
		assert.True(t, exec.Results[0].Success)
		assert.Equal(t, "hello", exec.Results[0].Output)

		assert.Equal(t, "task-002", exec.Results[1].TaskID)
		assert.True(t, exec.Results[1].Success)

		assert.Equal(t, "task-003", exec.Results[2].TaskID)
		assert.True(t, exec.Results[2].Success)

		// ResumeContext should be cleared after completion
		assert.Nil(t, exec.ResumeContext)

		// Only task-002 and task-003 should have been executed (check status)
		assert.Equal(t, types.TaskCompleted, exec.Tasks[1].Status)
		assert.Equal(t, types.TaskCompleted, exec.Tasks[2].Status)
	})

	t.Run("resumes from last task", func(t *testing.T) {
		robot := createRunTestRobot(t)
		exec := createRunTestExecution(robot)

		exec.Tasks = []types.Task{
			{
				ID:           "task-001",
				ExecutorType: types.ExecutorAssistant,
				ExecutorID:   "experts.text-writer",
				Messages: []agentcontext.Message{
					{Role: agentcontext.RoleUser, Content: "Write 'hello'"},
				},
				Order:  0,
				Status: types.TaskCompleted,
			},
			{
				ID:           "task-002",
				ExecutorType: types.ExecutorAssistant,
				ExecutorID:   "experts.text-writer",
				Messages: []agentcontext.Message{
					{Role: agentcontext.RoleUser, Content: "Write 'world'"},
				},
				Order:  1,
				Status: types.TaskWaitingInput,
			},
		}

		// Resume from the last task
		exec.ResumeContext = &types.ResumeContext{
			TaskIndex: 1,
			PreviousResults: []types.TaskResult{
				{TaskID: "task-001", Success: true, Output: "hello", Duration: 100},
			},
		}

		e := standard.New()
		err := e.RunExecution(ctx, exec, nil)

		require.NoError(t, err)
		require.Len(t, exec.Results, 2)
		assert.True(t, exec.Results[1].Success)
		assert.Equal(t, types.TaskCompleted, exec.Tasks[1].Status)
	})
}

// ============================================================================
// Suspend method tests (using Executor directly)
// ============================================================================

func TestSuspendExecution(t *testing.T) {
	t.Run("suspend sets waiting fields and returns ErrExecutionSuspended", func(t *testing.T) {
		robot := &types.Robot{
			MemberID:    "test-robot-suspend",
			TeamID:      "test-team-1",
			DisplayName: "Suspend Test Robot",
		}

		exec := &types.Execution{
			ID:       "exec-suspend-001",
			MemberID: robot.MemberID,
			TeamID:   robot.TeamID,
			Status:   types.ExecRunning,
			Phase:    types.PhaseRun,
			Tasks: []types.Task{
				{ID: "task-001", Status: types.TaskRunning},
				{ID: "task-002", Status: types.TaskPending},
			},
			Results: []types.TaskResult{},
		}
		exec.SetRobot(robot)

		e := standard.NewWithConfig(executortypes.Config{SkipPersistence: true})
		err := e.Suspend(
			types.NewContext(context.Background(), nil),
			exec, 0, "What time range?",
		)

		assert.ErrorIs(t, err, types.ErrExecutionSuspended)
		assert.Equal(t, types.ExecWaiting, exec.Status)
		assert.Equal(t, "task-001", exec.WaitingTaskID)
		assert.Equal(t, "What time range?", exec.WaitingQuestion)
		assert.NotNil(t, exec.WaitingSince)
		assert.NotNil(t, exec.ResumeContext)
		assert.Equal(t, 0, exec.ResumeContext.TaskIndex)
		assert.Equal(t, types.TaskWaitingInput, exec.Tasks[0].Status)
	})

	t.Run("suspend with out of range taskIndex is safe", func(t *testing.T) {
		robot := &types.Robot{
			MemberID: "test-robot-suspend-2",
			TeamID:   "test-team-1",
		}
		exec := &types.Execution{
			ID:       "exec-suspend-002",
			MemberID: robot.MemberID,
			TeamID:   robot.TeamID,
			Status:   types.ExecRunning,
			Tasks:    []types.Task{},
			Results:  []types.TaskResult{},
		}
		exec.SetRobot(robot)

		e := standard.NewWithConfig(executortypes.Config{SkipPersistence: true})
		err := e.Suspend(
			types.NewContext(context.Background(), nil),
			exec, 5, "some question",
		)

		assert.ErrorIs(t, err, types.ErrExecutionSuspended)
		assert.Equal(t, types.ExecWaiting, exec.Status)
		assert.Empty(t, exec.WaitingTaskID)
	})
}

// ============================================================================
// ExecuteWithControl handles ErrExecutionSuspended
// ============================================================================

func TestExecuteWithControlSuspend(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test")
	}

	testutils.Prepare(t)
	defer testutils.Clean(t)

	t.Run("returns ErrExecutionSuspended without marking as failed", func(t *testing.T) {
		// This test requires a robot-need-input assistant that returns need_input.
		// Since we don't have one yet (Stage 6), we test the suspend path indirectly
		// by verifying that when RunExecution returns ErrExecutionSuspended,
		// ExecuteWithControl propagates it correctly.
		//
		// Full E2E test with real assistant will be in Stage 6.

		robot := &types.Robot{
			MemberID:    "test-robot-suspend-exec",
			TeamID:      "test-team-1",
			DisplayName: "Suspend Exec Test",
			Config: &types.Config{
				Identity: &types.Identity{
					Role: "Test",
				},
				Resources: &types.Resources{
					Phases: map[types.Phase]string{},
					Agents: []string{"experts.text-writer"},
				},
				Quota: &types.Quota{Max: 5},
			},
		}

		ctx := types.NewContext(context.Background(), testAuth())
		e := standard.New()

		// Execute normally (no need_input expected from text-writer)
		exec, err := e.Execute(ctx, robot, types.TriggerHuman, "Write a greeting")
		if err == types.ErrExecutionSuspended {
			// If somehow suspended, verify state
			assert.Equal(t, types.ExecWaiting, exec.Status)
			assert.NotEmpty(t, exec.WaitingQuestion)
		} else {
			// Normal completion
			assert.NoError(t, err)
			assert.NotNil(t, exec)
		}
	})
}

// ============================================================================
// ResumeContext data structure tests
// ============================================================================

func TestResumeContext(t *testing.T) {
	t.Run("stores task index and previous results", func(t *testing.T) {
		rc := &types.ResumeContext{
			TaskIndex: 2,
			PreviousResults: []types.TaskResult{
				{TaskID: "t1", Success: true, Output: "out1"},
				{TaskID: "t2", Success: false, Error: "some error"},
			},
		}
		assert.Equal(t, 2, rc.TaskIndex)
		assert.Len(t, rc.PreviousResults, 2)
		assert.True(t, rc.PreviousResults[0].Success)
		assert.False(t, rc.PreviousResults[1].Success)
	})
}

// ============================================================================
// NeedInput in TaskResult
// ============================================================================

func TestTaskResultNeedInput(t *testing.T) {
	t.Run("NeedInput fields are populated correctly", func(t *testing.T) {
		result := types.TaskResult{
			TaskID:        "task-001",
			Success:       true,
			Output:        "some output",
			NeedInput:     true,
			InputQuestion: "What time range?",
		}
		assert.True(t, result.NeedInput)
		assert.Equal(t, "What time range?", result.InputQuestion)
	})
}

// ============================================================================
// Execution status transitions for suspend/resume
// ============================================================================

func TestExecutionStatusTransitions(t *testing.T) {
	t.Run("ExecWaiting is a valid status", func(t *testing.T) {
		exec := &types.Execution{
			ID:     "exec-001",
			Status: types.ExecWaiting,
		}
		assert.Equal(t, types.ExecStatus("waiting"), exec.Status)
	})

	t.Run("TaskWaitingInput is a valid task status", func(t *testing.T) {
		task := types.Task{
			ID:     "task-001",
			Status: types.TaskWaitingInput,
		}
		assert.Equal(t, types.TaskStatus("waiting_input"), task.Status)
	})

	t.Run("Execution V2 fields are accessible", func(t *testing.T) {
		now := time.Now()
		exec := &types.Execution{
			ID:              "exec-v2-001",
			ChatID:          "robot_member1_exec001",
			WaitingTaskID:   "task-002",
			WaitingQuestion: "What period?",
			WaitingSince:    &now,
			ResumeContext: &types.ResumeContext{
				TaskIndex: 1,
				PreviousResults: []types.TaskResult{
					{TaskID: "task-001", Success: true},
				},
			},
		}
		assert.Equal(t, "robot_member1_exec001", exec.ChatID)
		assert.Equal(t, "task-002", exec.WaitingTaskID)
		assert.Equal(t, "What period?", exec.WaitingQuestion)
		assert.NotNil(t, exec.WaitingSince)
		assert.NotNil(t, exec.ResumeContext)
		assert.Equal(t, 1, exec.ResumeContext.TaskIndex)
	})
}
