package standard_test

import (
	"context"
	"strings"
	"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"
	"github.com/yaoapp/yao/agent/robot/types"
	"github.com/yaoapp/yao/agent/testutils"
)

// ============================================================================
// P1 Goals Phase Tests
// ============================================================================

func TestRunGoalsBasic(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("generates goals from inspiration report (clock trigger)", func(t *testing.T) {
		// Create robot with goals agent configured
		robot := createGoalsTestRobot(t, "robot.goals")

		// Create execution with inspiration report (from P0)
		exec := createGoalsTestExecution(robot, types.TriggerClock)
		exec.Inspiration = &types.InspirationReport{
			Clock:   types.NewClockContext(time.Now(), ""),
			Content: "## Summary\nToday is Monday morning. Focus on weekly planning.\n\n## Highlights\n- New sales leads arrived\n- Weekly report due Friday",
		}

		// Run goals phase
		e := standard.New()
		err := e.RunGoals(ctx, exec, nil)

		require.NoError(t, err)
		require.NotNil(t, exec.Goals)
		assert.NotEmpty(t, exec.Goals.Content)
	})

	t.Run("includes priority markers in output", func(t *testing.T) {
		robot := createGoalsTestRobot(t, "robot.goals")
		exec := createGoalsTestExecution(robot, types.TriggerClock)
		exec.Inspiration = &types.InspirationReport{
			Clock:   types.NewClockContext(time.Now(), ""),
			Content: "## Summary\nUrgent: Customer complaint needs attention.\n\n## Highlights\n- Critical bug reported\n- Regular maintenance scheduled",
		}

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

		require.NoError(t, err)
		content := exec.Goals.Content

		// Verify expected structure in markdown output
		// Note: LLM output is non-deterministic, so we check for likely patterns
		hasGoals := strings.Contains(content, "Goal") ||
			strings.Contains(content, "##") ||
			strings.Contains(content, "High") ||
			strings.Contains(content, "Normal") ||
			strings.Contains(content, "1.")

		assert.True(t, hasGoals, "should contain goals structure, got: %s", content)
	})
}

func TestRunGoalsHumanTrigger(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("generates goals from human intervention", func(t *testing.T) {
		robot := createGoalsTestRobot(t, "robot.goals")
		exec := createGoalsTestExecution(robot, types.TriggerHuman)

		// Set human intervention input
		exec.Input = &types.TriggerInput{
			Action: "task.add",
			UserID: "user-123",
			Messages: []agentcontext.Message{
				{Role: agentcontext.RoleUser, Content: "Please analyze the Q4 sales data and prepare a summary report for the management meeting tomorrow."},
			},
		}

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

		require.NoError(t, err)
		require.NotNil(t, exec.Goals)
		assert.NotEmpty(t, exec.Goals.Content)

		// Goals should be related to the user request
		content := strings.ToLower(exec.Goals.Content)
		hasRelevantContent := strings.Contains(content, "sales") ||
			strings.Contains(content, "report") ||
			strings.Contains(content, "analysis") ||
			strings.Contains(content, "data") ||
			strings.Contains(content, "q4")

		assert.True(t, hasRelevantContent, "goals should relate to user request, got: %s", exec.Goals.Content)
	})

	t.Run("includes robot identity for human trigger", func(t *testing.T) {
		robot := &types.Robot{
			MemberID:    "test-robot-1",
			TeamID:      "test-team-1",
			DisplayName: "Sales Analyst",
			Config: &types.Config{
				Identity: &types.Identity{
					Role:   "Sales Analyst",
					Duties: []string{"Analyze sales data", "Generate reports"},
					Rules:  []string{"Focus on actionable insights"},
				},
				Resources: &types.Resources{
					Phases: map[types.Phase]string{
						types.PhaseGoals: "robot.goals",
					},
				},
			},
		}
		exec := createGoalsTestExecution(robot, types.TriggerHuman)
		exec.Input = &types.TriggerInput{
			Action: "instruct",
			Messages: []agentcontext.Message{
				{Role: agentcontext.RoleUser, Content: "What should I focus on today?"},
			},
		}

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

		require.NoError(t, err)
		assert.NotEmpty(t, exec.Goals.Content)
	})
}

func TestRunGoalsEventTrigger(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("generates goals from event trigger", func(t *testing.T) {
		robot := createGoalsTestRobot(t, "robot.goals")
		exec := createGoalsTestExecution(robot, types.TriggerEvent)

		// Set event input
		exec.Input = &types.TriggerInput{
			Source:    "webhook",
			EventType: "lead.created",
			Data: map[string]interface{}{
				"lead_id":      "lead-456",
				"company":      "BigCorp Inc",
				"contact_name": "John Smith",
				"email":        "john@bigcorp.com",
				"interest":     "Enterprise plan",
			},
		}

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

		require.NoError(t, err)
		require.NotNil(t, exec.Goals)
		assert.NotEmpty(t, exec.Goals.Content)

		// Goals should be related to the event
		content := strings.ToLower(exec.Goals.Content)
		hasRelevantContent := strings.Contains(content, "lead") ||
			strings.Contains(content, "bigcorp") ||
			strings.Contains(content, "contact") ||
			strings.Contains(content, "follow") ||
			strings.Contains(content, "qualify")

		assert.True(t, hasRelevantContent, "goals should relate to event, got: %s", exec.Goals.Content)
	})
}

func TestRunGoalsErrorHandling(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("returns error when robot is nil", func(t *testing.T) {
		exec := &types.Execution{
			ID:          "test-exec-1",
			TriggerType: types.TriggerClock,
		}
		// Don't set robot

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

		assert.Error(t, err)
		assert.Contains(t, err.Error(), "robot not found")
	})

	t.Run("returns error when agent not found", func(t *testing.T) {
		robot := &types.Robot{
			MemberID: "test-robot-1",
			TeamID:   "test-team-1",
			Config: &types.Config{
				Identity: &types.Identity{Role: "Test"},
				Resources: &types.Resources{
					Phases: map[types.Phase]string{
						types.PhaseGoals: "non.existent.agent",
					},
				},
			},
		}
		exec := createGoalsTestExecution(robot, types.TriggerClock)
		exec.Inspiration = &types.InspirationReport{
			Content: "Test content",
		}

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

		assert.Error(t, err)
		assert.Contains(t, err.Error(), "call failed")
	})

	t.Run("returns error when no input available and no identity", func(t *testing.T) {
		// Robot without identity - should fail when no input is provided
		robot := &types.Robot{
			MemberID: "test-robot-1",
			TeamID:   "test-team-1",
			Config: &types.Config{
				// No Identity - so no fallback content
				Resources: &types.Resources{
					Phases: map[types.Phase]string{
						types.PhaseGoals: "robot.goals",
					},
				},
			},
		}
		exec := createGoalsTestExecution(robot, types.TriggerHuman)
		exec.Input = nil // No input

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

		assert.Error(t, err)
		assert.Contains(t, err.Error(), "no input available")
	})
}

func TestRunGoalsFallbackBehavior(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("falls back to clock context when no inspiration report", func(t *testing.T) {
		robot := createGoalsTestRobot(t, "robot.goals")
		exec := createGoalsTestExecution(robot, types.TriggerClock)
		exec.Inspiration = nil // No inspiration report

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

		// Should still work with fallback clock context
		require.NoError(t, err)
		require.NotNil(t, exec.Goals)
		assert.NotEmpty(t, exec.Goals.Content)
	})
}

// ============================================================================
// Delivery Parsing Tests
// ============================================================================

func TestParseDeliveryFromGoalsResponse(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("parses delivery when agent returns it", func(t *testing.T) {
		robot := createGoalsTestRobot(t, "robot.goals")
		exec := createGoalsTestExecution(robot, types.TriggerHuman)

		// Request that explicitly asks for email delivery
		exec.Input = &types.TriggerInput{
			Action: "task.add",
			Messages: []agentcontext.Message{
				{Role: agentcontext.RoleUser, Content: "Prepare a sales report and send it to team@example.com via email"},
			},
		}

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

		require.NoError(t, err)
		require.NotNil(t, exec.Goals)
		assert.NotEmpty(t, exec.Goals.Content)

		// Delivery may or may not be present depending on LLM response
		// If present, verify structure
		if exec.Goals.Delivery != nil {
			// Type should be valid if present
			if exec.Goals.Delivery.Type != "" {
				validTypes := []types.DeliveryType{
					types.DeliveryEmail, types.DeliveryWebhook,
					types.DeliveryProcess, types.DeliveryNotify,
				}
				found := false
				for _, vt := range validTypes {
					if exec.Goals.Delivery.Type == vt {
						found = true
						break
					}
				}
				// Note: LLM might return non-standard types, we accept them but log
				t.Logf("Delivery type: %s (valid: %v)", exec.Goals.Delivery.Type, found)
			}
		}
	})
}

func TestDeliveryTypeValidation(t *testing.T) {
	t.Run("valid delivery types", func(t *testing.T) {
		validTypes := []types.DeliveryType{
			types.DeliveryEmail,
			types.DeliveryWebhook,
			types.DeliveryProcess,
			types.DeliveryNotify,
		}

		for _, dt := range validTypes {
			assert.True(t, standard.IsValidDeliveryType(dt), "should be valid: %s", dt)
		}
	})

	t.Run("invalid delivery types", func(t *testing.T) {
		invalidTypes := []types.DeliveryType{
			"invalid",
			"sms",
			"",
		}

		for _, dt := range invalidTypes {
			assert.False(t, standard.IsValidDeliveryType(dt), "should be invalid: %s", dt)
		}
	})
}

func TestParseDelivery(t *testing.T) {
	t.Run("parses valid delivery with all fields", func(t *testing.T) {
		data := map[string]interface{}{
			"type":       "email",
			"recipients": []interface{}{"user@example.com", "team@example.com"},
			"format":     "markdown",
			"template":   "weekly-report",
			"options": map[string]interface{}{
				"subject": "Weekly Report",
			},
		}

		result := standard.ParseDelivery(data)

		require.NotNil(t, result)
		assert.Equal(t, types.DeliveryEmail, result.Type)
		assert.Equal(t, []string{"user@example.com", "team@example.com"}, result.Recipients)
		assert.Equal(t, "markdown", result.Format)
		assert.Equal(t, "weekly-report", result.Template)
		assert.Equal(t, "Weekly Report", result.Options["subject"])
	})

	t.Run("returns nil for nil data", func(t *testing.T) {
		result := standard.ParseDelivery(nil)
		assert.Nil(t, result)
	})

	t.Run("returns nil for missing type", func(t *testing.T) {
		data := map[string]interface{}{
			"recipients": []interface{}{"user@example.com"},
		}

		result := standard.ParseDelivery(data)
		assert.Nil(t, result)
	})

	t.Run("returns nil for empty type", func(t *testing.T) {
		data := map[string]interface{}{
			"type":       "",
			"recipients": []interface{}{"user@example.com"},
		}

		result := standard.ParseDelivery(data)
		assert.Nil(t, result)
	})

	t.Run("returns nil for invalid type", func(t *testing.T) {
		data := map[string]interface{}{
			"type":       "sms",
			"recipients": []interface{}{"user@example.com"},
		}

		result := standard.ParseDelivery(data)
		assert.Nil(t, result)
	})

	t.Run("handles missing optional fields", func(t *testing.T) {
		data := map[string]interface{}{
			"type": "webhook",
		}

		result := standard.ParseDelivery(data)

		require.NotNil(t, result)
		assert.Equal(t, types.DeliveryWebhook, result.Type)
		assert.Empty(t, result.Recipients)
		assert.Empty(t, result.Format)
		assert.Empty(t, result.Template)
		assert.Nil(t, result.Options)
	})

	t.Run("handles non-string recipients gracefully", func(t *testing.T) {
		data := map[string]interface{}{
			"type":       "email",
			"recipients": []interface{}{"valid@example.com", 123, nil, "another@example.com"},
		}

		result := standard.ParseDelivery(data)

		require.NotNil(t, result)
		// Only string recipients should be included
		assert.Equal(t, []string{"valid@example.com", "another@example.com"}, result.Recipients)
	})

	t.Run("parses all valid delivery types", func(t *testing.T) {
		validTypes := []string{"email", "webhook", "process", "notify"}

		for _, dt := range validTypes {
			data := map[string]interface{}{
				"type": dt,
			}

			result := standard.ParseDelivery(data)
			require.NotNil(t, result, "should parse type: %s", dt)
			assert.Equal(t, types.DeliveryType(dt), result.Type)
		}
	})
}

// ============================================================================
// InputFormatter Tests for P1
// ============================================================================

func TestInputFormatterFormatRobotIdentity(t *testing.T) {
	formatter := standard.NewInputFormatter()

	t.Run("formats robot identity correctly", func(t *testing.T) {
		robot := &types.Robot{
			MemberID: "test-robot",
			Config: &types.Config{
				Identity: &types.Identity{
					Role:   "Sales Analyst",
					Duties: []string{"Analyze sales data", "Generate reports"},
					Rules:  []string{"Be accurate", "Be concise"},
				},
			},
		}

		content := formatter.FormatRobotIdentity(robot)

		assert.Contains(t, content, "## Robot Identity")
		assert.Contains(t, content, "Sales Analyst")
		assert.Contains(t, content, "Analyze sales data")
		assert.Contains(t, content, "Generate reports")
		assert.Contains(t, content, "Be accurate")
		assert.Contains(t, content, "Be concise")
	})

	t.Run("returns empty for nil robot", func(t *testing.T) {
		content := formatter.FormatRobotIdentity(nil)
		assert.Empty(t, content)
	})

	t.Run("returns empty for robot without config", func(t *testing.T) {
		robot := &types.Robot{MemberID: "test"}
		content := formatter.FormatRobotIdentity(robot)
		assert.Empty(t, content)
	})

	t.Run("returns empty for robot without identity", func(t *testing.T) {
		robot := &types.Robot{
			MemberID: "test",
			Config:   &types.Config{},
		}
		content := formatter.FormatRobotIdentity(robot)
		assert.Empty(t, content)
	})

	t.Run("handles identity with only role", func(t *testing.T) {
		robot := &types.Robot{
			MemberID: "test",
			Config: &types.Config{
				Identity: &types.Identity{
					Role: "Simple Bot",
				},
			},
		}

		content := formatter.FormatRobotIdentity(robot)

		assert.Contains(t, content, "## Robot Identity")
		assert.Contains(t, content, "Simple Bot")
		assert.NotContains(t, content, "Duties")
		assert.NotContains(t, content, "Rules")
	})
}

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

// createGoalsTestRobot creates a test robot with specified goals agent
// Includes available expert agents so the Goals Agent knows what resources are available
//
// Note: The agent IDs listed in Resources.Agents must exist in yao-dev-app/assistants/experts/
// Current available experts: data-analyst, summarizer, text-writer, web-reader
func createGoalsTestRobot(t *testing.T, agentID string) *types.Robot {
	t.Helper()
	return &types.Robot{
		MemberID:    "test-robot-1",
		TeamID:      "test-team-1",
		DisplayName: "Test Robot",
		Config: &types.Config{
			Identity: &types.Identity{
				Role:   "Test Assistant",
				Duties: []string{"Testing", "Validation", "Data Analysis", "Report Generation"},
			},
			Resources: &types.Resources{
				Phases: map[types.Phase]string{
					types.PhaseGoals: agentID,
				},
				// Available expert agents that can be delegated to
				// These IDs correspond to assistants in yao-dev-app/assistants/experts/
				Agents: []string{
					"experts.data-analyst", // Data analysis and insights
					"experts.summarizer",   // Content summarization
					"experts.text-writer",  // Report and document generation
					"experts.web-reader",   // Web content extraction
				},
			},
			// Knowledge base collections (if any)
			KB: &types.KB{
				Collections: []string{"test-knowledge"},
			},
		},
	}
}

// createGoalsTestExecution creates a test execution for goals phase
func createGoalsTestExecution(robot *types.Robot, trigger types.TriggerType) *types.Execution {
	exec := &types.Execution{
		ID:          "test-exec-goals-1",
		MemberID:    robot.MemberID,
		TeamID:      robot.TeamID,
		TriggerType: trigger,
		StartTime:   time.Now(),
		Status:      types.ExecRunning,
		Phase:       types.PhaseGoals,
	}
	exec.SetRobot(robot)
	return exec
}
