package standard_test

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/yaoapp/yao/agent/robot/executor/standard"
	"github.com/yaoapp/yao/agent/robot/executor/types"
	"github.com/yaoapp/yao/agent/robot/store"
	robottypes "github.com/yaoapp/yao/agent/robot/types"
	"github.com/yaoapp/yao/agent/testutils"
	oauthTypes "github.com/yaoapp/yao/openapi/oauth/types"
)

// ============================================================================
// Executor Persistence Integration Tests
// ============================================================================

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

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

	t.Run("persists_execution_record_on_start", func(t *testing.T) {
		ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
			UserID: "user_persist_001",
			TeamID: "team_persist_001",
		})

		robot := createPersistenceTestRobot("member_persist_001", "team_persist_001")

		// Create executor with persistence enabled
		e := standard.NewWithConfig(types.Config{
			SkipPersistence: false,
		})

		// Execute with simulated failure to ensure we get a result
		exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, "simulate_failure")
		require.NoError(t, err)
		require.NotNil(t, exec)

		// Verify execution record was persisted
		s := store.NewExecutionStore()
		record, err := s.Get(context.Background(), exec.ID)
		require.NoError(t, err)
		require.NotNil(t, record)

		assert.Equal(t, exec.ID, record.ExecutionID)
		assert.Equal(t, "member_persist_001", record.MemberID)
		assert.Equal(t, "team_persist_001", record.TeamID)
		assert.Equal(t, robottypes.TriggerHuman, record.TriggerType)
		assert.Equal(t, robottypes.ExecFailed, record.Status)
		assert.Equal(t, "simulated failure", record.Error)

		// Cleanup
		_ = s.Delete(context.Background(), exec.ID)
	})

	t.Run("persists_failed_status_with_error", func(t *testing.T) {
		ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
			UserID: "user_persist_002",
			TeamID: "team_persist_002",
		})

		robot := createPersistenceTestRobot("member_persist_002", "team_persist_002")

		e := standard.NewWithConfig(types.Config{
			SkipPersistence: false,
		})

		// Execute with simulated failure
		exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, "simulate_failure")
		require.NoError(t, err)
		require.NotNil(t, exec)

		// Verify the record has failed status with error message
		s := store.NewExecutionStore()
		record, err := s.Get(context.Background(), exec.ID)
		require.NoError(t, err)
		require.NotNil(t, record)

		assert.Equal(t, robottypes.ExecFailed, record.Status)
		assert.Equal(t, "simulated failure", record.Error)
		assert.NotNil(t, record.StartTime)

		// Cleanup
		_ = s.Delete(context.Background(), exec.ID)
	})

	t.Run("skips_persistence_when_disabled", func(t *testing.T) {
		ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
			UserID: "user_persist_003",
			TeamID: "team_persist_003",
		})

		robot := createPersistenceTestRobot("member_persist_003", "team_persist_003")

		// Create executor with persistence disabled
		e := standard.NewWithConfig(types.Config{
			SkipPersistence: true,
		})

		exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, "simulate_failure")
		require.NoError(t, err)
		require.NotNil(t, exec)

		// Verify no record was created
		s := store.NewExecutionStore()
		record, err := s.Get(context.Background(), exec.ID)
		require.NoError(t, err)
		assert.Nil(t, record) // Should not exist
	})
}

// ============================================================================
// Goals Injection Tests (Host Agent confirmed goals)
// ============================================================================

// TestExecutorGoalsInjection verifies that when TriggerHuman is used with
// pre-confirmed goals (from Host Agent via /v1/agent/robots/:id/execute),
// the goals are injected directly into exec.Goals before RunGoals runs,
// and are persisted (title updated) so the task list shows the correct title.
func TestExecutorGoalsInjection(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test")
	}

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

	t.Run("goals_injected_from_trigger_input_data", func(t *testing.T) {
		ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
			UserID: "user_goals_inject_001",
			TeamID: "team_goals_inject_001",
		})

		robot := createPersistenceTestRobot("member_goals_inject_001", "team_goals_inject_001")

		e := standard.NewWithConfig(types.Config{
			SkipPersistence: false,
		})

		// Simulate Host Agent confirmed goals passed via TriggerInput.Data
		triggerInput := &robottypes.TriggerInput{
			Data: map[string]interface{}{
				"goals":   "Create a mecha image with sci-fi style",
				"chat_id": "robot_member_goals_inject_001_1234567890",
			},
		}

		exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, triggerInput)
		require.NoError(t, err)
		require.NotNil(t, exec)

		// Goals should be injected from TriggerInput.Data
		require.NotNil(t, exec.Goals, "Goals should be injected from TriggerInput.Data")
		assert.Equal(t, "Create a mecha image with sci-fi style", exec.Goals.Content,
			"Goals content should match the pre-confirmed goals")

		// Verify goals were persisted to the store
		s := store.NewExecutionStore()
		record, err := s.Get(context.Background(), exec.ID)
		require.NoError(t, err)
		require.NotNil(t, record)

		// Execution name should reflect the goals (not "Preparing...")
		assert.NotEmpty(t, exec.Name, "Execution name should be set from goals")
		assert.NotEqual(t, "Preparing...", exec.Name, "Name should not be the default placeholder")

		// Cleanup
		_ = s.Delete(context.Background(), exec.ID)
		t.Logf("✓ Goals injected from TriggerInput.Data: goals=%q, name=%q",
			exec.Goals.Content, exec.Name)
	})

	t.Run("empty_goals_falls_through_to_goals_agent", func(t *testing.T) {
		// When TriggerInput.Data["goals"] is an empty string, the executor
		// does NOT inject pre-confirmed goals and falls through to RunGoals.
		// RunGoals will call the Goals Agent (LLM), which may succeed or fail
		// depending on the environment. We only verify that the executor returns
		// without a panic and that no pre-confirmed goals were force-injected.
		//
		// This test requires a running AI environment; skip in short mode.
		if testing.Short() {
			t.Skip("Skipping: requires LLM for RunGoals fallback")
		}

		ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
			UserID: "user_goals_empty_002",
			TeamID: "team_goals_empty_002",
		})

		robot := createPersistenceTestRobot("member_goals_empty_002", "team_goals_empty_002")

		e := standard.NewWithConfig(types.Config{
			SkipPersistence: true,
		})

		triggerInput := &robottypes.TriggerInput{
			Data: map[string]interface{}{
				"goals": "", // empty — should not be injected as pre-confirmed
			},
		}

		exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, triggerInput)
		require.NoError(t, err)
		require.NotNil(t, exec)

		// If Goals was set, it came from the Goals Agent, NOT from the empty string injection.
		// Either nil (agent skipped) or non-nil (agent ran) is acceptable.
		if exec.Goals != nil {
			assert.NotEmpty(t, exec.Goals.Content,
				"If Goals Agent ran, content should be non-empty")
		}
		t.Logf("✓ Empty goals falls through to Goals Agent (goals=%v)", exec.Goals != nil)
	})

	t.Run("no_trigger_input_uses_normal_flow", func(t *testing.T) {
		ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
			UserID: "user_goals_normal_001",
			TeamID: "team_goals_normal_001",
		})

		robot := createPersistenceTestRobot("member_goals_normal_001", "team_goals_normal_001")

		e := standard.NewWithConfig(types.Config{
			SkipPersistence: true,
		})

		// No TriggerInput — simulate plain string fallback (old API usage)
		exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, "simulate_failure")
		require.NoError(t, err)
		require.NotNil(t, exec)

		// Goals nil is expected — RunGoals would normally call the LLM
		assert.Nil(t, exec.Goals, "Without pre-confirmed goals, Goals should remain nil")
		t.Logf("✓ Normal flow (no pre-confirmed goals) proceeds without injection")
	})
}

// ============================================================================
// Helper Functions
// ============================================================================

func createPersistenceTestRobot(memberID, teamID string) *robottypes.Robot {
	return &robottypes.Robot{
		MemberID:       memberID,
		TeamID:         teamID,
		DisplayName:    "Persistence Test Robot",
		Status:         robottypes.RobotIdle,
		AutonomousMode: true,
		Config: &robottypes.Config{
			Identity: &robottypes.Identity{
				Role:   "Test Robot",
				Duties: []string{"Testing persistence"},
			},
			Quota: &robottypes.Quota{
				Max:   5,
				Queue: 10,
			},
			Triggers: &robottypes.Triggers{
				Intervene: &robottypes.TriggerSwitch{Enabled: true},
			},
			Resources: &robottypes.Resources{
				Phases: map[robottypes.Phase]string{
					robottypes.PhaseInspiration: "robot.inspiration",
					robottypes.PhaseGoals:       "robot.goals",
					robottypes.PhaseTasks:       "robot.tasks",
					robottypes.PhaseRun:         "robot.validation",
					"validation":                "robot.validation",
				},
				Agents: []string{"experts.text-writer", "experts.data-analyst"},
			},
		},
	}
}
