package manager_test

// Integration tests for execution control (Pause/Resume/Stop)
// Tests Manager's execution control methods and ExecutionController

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/yaoapp/gou/model"
	"github.com/yaoapp/xun/capsule"
	agentcontext "github.com/yaoapp/yao/agent/context"
	"github.com/yaoapp/yao/agent/robot/manager"
	"github.com/yaoapp/yao/agent/robot/pool"
	"github.com/yaoapp/yao/agent/robot/types"
	"github.com/yaoapp/yao/agent/testutils"
)

// ==================== Pause/Resume Tests ====================

// TestIntegrationExecutionPauseResume tests pausing and resuming executions
func TestIntegrationExecutionPauseResume(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test")
	}

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

	cleanupIntegrationRobots(t)
	defer cleanupIntegrationRobots(t)

	t.Run("pause and resume execution", func(t *testing.T) {
		setupControlTestRobot(t, "robot_integ_ctrl_pause", "team_integ_ctrl")

		// Use slow executor to have time to pause
		exec := &slowExecutor{delay: 500 * time.Millisecond}

		config := &manager.Config{
			TickInterval: 100 * time.Millisecond,
			PoolConfig:   &pool.Config{WorkerSize: 3, QueueSize: 20},
		}
		m := manager.NewWithConfig(config)
		m.Pool().SetExecutor(exec)

		err := m.Start()
		require.NoError(t, err)
		defer m.Stop()

		// Verify robot is loaded into cache
		robot := m.Cache().Get("robot_integ_ctrl_pause")
		require.NotNil(t, robot, "Robot should be loaded into cache")

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

		// Trigger execution
		req := &types.InterveneRequest{
			MemberID: "robot_integ_ctrl_pause",
			Action:   types.ActionTaskAdd,
			Messages: []agentcontext.Message{
				{Role: agentcontext.RoleUser, Content: "Test task"},
			},
		}
		result, err := m.Intervene(ctx, req)
		require.NoError(t, err)
		execID := result.ExecutionID

		// Wait for execution to be tracked
		time.Sleep(100 * time.Millisecond)

		// Pause execution
		err = m.PauseExecution(ctx, execID)
		assert.NoError(t, err)

		// Verify paused
		status, err := m.GetExecutionStatus(execID)
		assert.NoError(t, err)
		assert.True(t, status.IsPaused(), "Execution should be paused")

		// Resume execution
		err = m.ResumeExecution(ctx, execID)
		assert.NoError(t, err)

		// Verify resumed
		status, err = m.GetExecutionStatus(execID)
		assert.NoError(t, err)
		assert.False(t, status.IsPaused(), "Execution should be resumed")
	})

	t.Run("pause non-existent execution", func(t *testing.T) {
		m := manager.New()
		err := m.Start()
		require.NoError(t, err)
		defer m.Stop()

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

		err = m.PauseExecution(ctx, "nonexistent_exec")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "not found")
	})

	t.Run("resume non-paused execution", func(t *testing.T) {
		setupControlTestRobot(t, "robot_integ_ctrl_resume", "team_integ_ctrl")

		exec := &slowExecutor{delay: 500 * time.Millisecond}

		config := &manager.Config{
			TickInterval: 100 * time.Millisecond,
			PoolConfig:   &pool.Config{WorkerSize: 3, QueueSize: 20},
		}
		m := manager.NewWithConfig(config)
		m.Pool().SetExecutor(exec)

		err := m.Start()
		require.NoError(t, err)
		defer m.Stop()

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

		// Trigger execution
		req := &types.InterveneRequest{
			MemberID: "robot_integ_ctrl_resume",
			Action:   types.ActionTaskAdd,
			Messages: []agentcontext.Message{
				{Role: agentcontext.RoleUser, Content: "Test task"},
			},
		}
		result, err := m.Intervene(ctx, req)
		require.NoError(t, err)
		execID := result.ExecutionID

		// Wait for execution to be tracked
		time.Sleep(100 * time.Millisecond)

		// Resume without pausing first - should be safe
		err = m.ResumeExecution(ctx, execID)
		// May or may not error depending on implementation
		// The important thing is it doesn't panic
	})
}

// ==================== Stop Tests ====================

// TestIntegrationExecutionStop tests stopping executions
func TestIntegrationExecutionStop(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test")
	}

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

	cleanupIntegrationRobots(t)
	defer cleanupIntegrationRobots(t)

	t.Run("stop execution", func(t *testing.T) {
		setupControlTestRobot(t, "robot_integ_ctrl_stop", "team_integ_ctrl")

		exec := &slowExecutor{delay: 1 * time.Second}

		config := &manager.Config{
			TickInterval: 100 * time.Millisecond,
			PoolConfig:   &pool.Config{WorkerSize: 3, QueueSize: 20},
		}
		m := manager.NewWithConfig(config)
		m.Pool().SetExecutor(exec)

		err := m.Start()
		require.NoError(t, err)
		defer m.Stop()

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

		// Trigger execution
		req := &types.InterveneRequest{
			MemberID: "robot_integ_ctrl_stop",
			Action:   types.ActionTaskAdd,
			Messages: []agentcontext.Message{
				{Role: agentcontext.RoleUser, Content: "Test task"},
			},
		}
		result, err := m.Intervene(ctx, req)
		require.NoError(t, err)
		execID := result.ExecutionID

		// Wait for execution to be tracked
		time.Sleep(100 * time.Millisecond)

		// Stop execution
		err = m.StopExecution(ctx, execID)
		assert.NoError(t, err)

		// Execution should be removed from tracking
		_, err = m.GetExecutionStatus(execID)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "not found")
	})

	t.Run("stop non-existent execution", func(t *testing.T) {
		m := manager.New()
		err := m.Start()
		require.NoError(t, err)
		defer m.Stop()

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

		err = m.StopExecution(ctx, "nonexistent_exec")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "not found")
	})
}

// ==================== List Executions Tests ====================

// TestIntegrationListExecutions tests listing executions
func TestIntegrationListExecutions(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test")
	}

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

	cleanupIntegrationRobots(t)
	defer cleanupIntegrationRobots(t)

	t.Run("list all executions", func(t *testing.T) {
		setupControlTestRobot(t, "robot_integ_ctrl_list1", "team_integ_ctrl")
		setupControlTestRobot(t, "robot_integ_ctrl_list2", "team_integ_ctrl")

		exec := &slowExecutor{delay: 500 * time.Millisecond}

		config := &manager.Config{
			TickInterval: 100 * time.Millisecond,
			PoolConfig:   &pool.Config{WorkerSize: 5, QueueSize: 20},
		}
		m := manager.NewWithConfig(config)
		m.Pool().SetExecutor(exec)

		err := m.Start()
		require.NoError(t, err)
		defer m.Stop()

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

		// Trigger multiple executions
		execIDs := make([]string, 0)
		for _, memberID := range []string{"robot_integ_ctrl_list1", "robot_integ_ctrl_list2"} {
			req := &types.InterveneRequest{
				MemberID: memberID,
				Action:   types.ActionTaskAdd,
				Messages: []agentcontext.Message{
					{Role: agentcontext.RoleUser, Content: "Test task"},
				},
			}
			result, err := m.Intervene(ctx, req)
			require.NoError(t, err)
			execIDs = append(execIDs, result.ExecutionID)
		}

		// Wait for executions to be tracked
		time.Sleep(100 * time.Millisecond)

		// List all executions
		execs := m.ListExecutions()
		assert.GreaterOrEqual(t, len(execs), 2, "Should have at least 2 executions")

		// Verify our executions are in the list
		foundCount := 0
		for _, e := range execs {
			for _, id := range execIDs {
				if e.ID == id {
					foundCount++
				}
			}
		}
		assert.Equal(t, 2, foundCount, "Both executions should be in list")
	})

	t.Run("list executions by member", func(t *testing.T) {
		setupControlTestRobot(t, "robot_integ_ctrl_member1", "team_integ_ctrl")
		setupControlTestRobot(t, "robot_integ_ctrl_member2", "team_integ_ctrl")

		exec := &slowExecutor{delay: 500 * time.Millisecond}

		config := &manager.Config{
			TickInterval: 100 * time.Millisecond,
			PoolConfig:   &pool.Config{WorkerSize: 5, QueueSize: 20},
		}
		m := manager.NewWithConfig(config)
		m.Pool().SetExecutor(exec)

		err := m.Start()
		require.NoError(t, err)
		defer m.Stop()

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

		// Trigger 3 executions for robot 1
		for i := 0; i < 3; i++ {
			req := &types.InterveneRequest{
				MemberID: "robot_integ_ctrl_member1",
				Action:   types.ActionTaskAdd,
				Messages: []agentcontext.Message{
					{Role: agentcontext.RoleUser, Content: "Test task"},
				},
			}
			_, err := m.Intervene(ctx, req)
			require.NoError(t, err)
		}

		// Trigger 2 executions for robot 2
		for i := 0; i < 2; i++ {
			req := &types.InterveneRequest{
				MemberID: "robot_integ_ctrl_member2",
				Action:   types.ActionTaskAdd,
				Messages: []agentcontext.Message{
					{Role: agentcontext.RoleUser, Content: "Test task"},
				},
			}
			_, err := m.Intervene(ctx, req)
			require.NoError(t, err)
		}

		// Wait for executions to be tracked
		time.Sleep(100 * time.Millisecond)

		// List executions for robot 1
		execs1 := m.ListExecutionsByMember("robot_integ_ctrl_member1")
		assert.GreaterOrEqual(t, len(execs1), 1, "Robot 1 should have executions")

		// List executions for robot 2
		execs2 := m.ListExecutionsByMember("robot_integ_ctrl_member2")
		assert.GreaterOrEqual(t, len(execs2), 1, "Robot 2 should have executions")

		// Verify member IDs
		for _, e := range execs1 {
			assert.Equal(t, "robot_integ_ctrl_member1", e.MemberID)
		}
		for _, e := range execs2 {
			assert.Equal(t, "robot_integ_ctrl_member2", e.MemberID)
		}
	})
}

// ==================== Multiple Control Operations Tests ====================

// TestIntegrationMultipleControlOperations tests sequences of control operations
func TestIntegrationMultipleControlOperations(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test")
	}

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

	cleanupIntegrationRobots(t)
	defer cleanupIntegrationRobots(t)

	t.Run("pause-resume-pause-stop sequence", func(t *testing.T) {
		setupControlTestRobot(t, "robot_integ_ctrl_seq", "team_integ_ctrl")

		exec := &slowExecutor{delay: 2 * time.Second}

		config := &manager.Config{
			TickInterval: 100 * time.Millisecond,
			PoolConfig:   &pool.Config{WorkerSize: 3, QueueSize: 20},
		}
		m := manager.NewWithConfig(config)
		m.Pool().SetExecutor(exec)

		err := m.Start()
		require.NoError(t, err)
		defer m.Stop()

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

		// Trigger execution
		req := &types.InterveneRequest{
			MemberID: "robot_integ_ctrl_seq",
			Action:   types.ActionTaskAdd,
			Messages: []agentcontext.Message{
				{Role: agentcontext.RoleUser, Content: "Test task"},
			},
		}
		result, err := m.Intervene(ctx, req)
		require.NoError(t, err)
		execID := result.ExecutionID

		// Wait for tracking
		time.Sleep(100 * time.Millisecond)

		// Pause
		err = m.PauseExecution(ctx, execID)
		assert.NoError(t, err)
		status, _ := m.GetExecutionStatus(execID)
		assert.True(t, status.IsPaused())

		// Resume
		err = m.ResumeExecution(ctx, execID)
		assert.NoError(t, err)
		status, _ = m.GetExecutionStatus(execID)
		assert.False(t, status.IsPaused())

		// Pause again
		err = m.PauseExecution(ctx, execID)
		assert.NoError(t, err)
		status, _ = m.GetExecutionStatus(execID)
		assert.True(t, status.IsPaused())

		// Stop
		err = m.StopExecution(ctx, execID)
		assert.NoError(t, err)
		_, err = m.GetExecutionStatus(execID)
		assert.Error(t, err) // Should be removed
	})

	t.Run("concurrent control operations", func(t *testing.T) {
		setupControlTestRobot(t, "robot_integ_ctrl_conc", "team_integ_ctrl")

		exec := &slowExecutor{delay: 1 * time.Second}

		config := &manager.Config{
			TickInterval: 100 * time.Millisecond,
			PoolConfig:   &pool.Config{WorkerSize: 3, QueueSize: 20},
		}
		m := manager.NewWithConfig(config)
		m.Pool().SetExecutor(exec)

		err := m.Start()
		require.NoError(t, err)
		defer m.Stop()

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

		// Trigger execution
		req := &types.InterveneRequest{
			MemberID: "robot_integ_ctrl_conc",
			Action:   types.ActionTaskAdd,
			Messages: []agentcontext.Message{
				{Role: agentcontext.RoleUser, Content: "Test task"},
			},
		}
		result, err := m.Intervene(ctx, req)
		require.NoError(t, err)
		execID := result.ExecutionID

		// Wait for tracking
		time.Sleep(100 * time.Millisecond)

		// Concurrent pause/resume operations should not panic
		var wg sync.WaitGroup
		for i := 0; i < 10; i++ {
			wg.Add(2)
			go func() {
				defer wg.Done()
				m.PauseExecution(ctx, execID)
			}()
			go func() {
				defer wg.Done()
				m.ResumeExecution(ctx, execID)
			}()
		}

		// Wait with timeout
		done := make(chan struct{})
		go func() {
			wg.Wait()
			close(done)
		}()

		select {
		case <-done:
			// Success - no deadlock
		case <-time.After(5 * time.Second):
			t.Fatal("Concurrent control operations caused deadlock")
		}
	})
}

// ==================== Helper Types ====================

// slowExecutor is an executor with configurable delay
type slowExecutor struct {
	delay   time.Duration
	count   int32
	current int32
}

func (e *slowExecutor) Execute(ctx *types.Context, robot *types.Robot, trigger types.TriggerType, data interface{}) (*types.Execution, error) {
	return e.ExecuteWithControl(ctx, robot, trigger, data, "", nil)
}

func (e *slowExecutor) ExecuteWithID(ctx *types.Context, robot *types.Robot, trigger types.TriggerType, data interface{}, execID string) (*types.Execution, error) {
	return e.ExecuteWithControl(ctx, robot, trigger, data, execID, nil)
}

func (e *slowExecutor) ExecuteWithControl(ctx *types.Context, robot *types.Robot, trigger types.TriggerType, data interface{}, execID string, control types.ExecutionControl) (*types.Execution, error) {
	if robot == nil {
		return nil, types.ErrRobotNotFound
	}

	// Use provided execID or generate one
	if execID == "" {
		execID = "exec_slow_" + robot.MemberID
	}
	exec := &types.Execution{
		ID:          execID,
		MemberID:    robot.MemberID,
		TeamID:      robot.TeamID,
		TriggerType: trigger,
		StartTime:   time.Now(),
		Status:      types.ExecPending,
	}

	if !robot.TryAcquireSlot(exec) {
		return nil, types.ErrQuotaExceeded
	}
	defer robot.RemoveExecution(exec.ID)

	atomic.AddInt32(&e.current, 1)
	defer atomic.AddInt32(&e.current, -1)

	exec.Status = types.ExecRunning
	time.Sleep(e.delay)

	exec.Status = types.ExecCompleted
	now := time.Now()
	exec.EndTime = &now

	atomic.AddInt32(&e.count, 1)
	return exec, nil
}

func (e *slowExecutor) ExecCount() int {
	return int(atomic.LoadInt32(&e.count))
}

func (e *slowExecutor) CurrentCount() int {
	return int(atomic.LoadInt32(&e.current))
}

func (e *slowExecutor) Resume(ctx *types.Context, execID string, reply string) error {
	return fmt.Errorf("resume not supported in slow executor")
}

func (e *slowExecutor) Reset() {
	atomic.StoreInt32(&e.count, 0)
	atomic.StoreInt32(&e.current, 0)
}

// ==================== Test Data Setup Helpers ====================

// setupControlTestRobot creates a robot for control testing
func setupControlTestRobot(t *testing.T, memberID, teamID string) {
	m := model.Select("__yao.member")
	tableName := m.MetaData.Table.Name
	qb := capsule.Query()

	robotConfig := map[string]interface{}{
		"identity": map[string]interface{}{
			"role":   "Control Test Robot",
			"duties": []string{"Test execution control"},
		},
		"quota": map[string]interface{}{
			"max":      5,
			"queue":    20,
			"priority": 5,
		},
		"triggers": map[string]interface{}{
			"clock":     map[string]interface{}{"enabled": true},
			"intervene": map[string]interface{}{"enabled": true},
			"event":     map[string]interface{}{"enabled": true},
		},
	}
	configJSON, _ := json.Marshal(robotConfig)

	err := qb.Table(tableName).Insert([]map[string]interface{}{
		{
			"member_id":       memberID,
			"team_id":         teamID,
			"member_type":     "robot",
			"display_name":    "Control Test Robot " + memberID,
			"status":          "active",
			"role_id":         "member",
			"autonomous_mode": true,
			"robot_status":    "idle",
			"robot_config":    string(configJSON),
		},
	})
	if err != nil {
		t.Fatalf("Failed to insert %s: %v", memberID, err)
	}
}
