package api_test

import (
	"context"
	"encoding/json"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/yaoapp/gou/model"
	"github.com/yaoapp/xun/capsule"
	"github.com/yaoapp/yao/agent/robot/api"
	"github.com/yaoapp/yao/agent/robot/executor/standard"
	"github.com/yaoapp/yao/agent/robot/types"
	"github.com/yaoapp/yao/agent/testutils"
)

// TestE2EInteractNewAssignment tests the full Interact flow for a new task assignment.
// With the conversational Host Agent, the first turn may return natural language
// (waiting_for_more) or an action decision depending on request clarity.
func TestE2EInteractNewAssignment(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping E2E test - requires real LLM calls")
	}

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

	cleanupInteractRobots(t)
	cleanupInteractExecutions(t)
	defer cleanupInteractRobots(t)
	defer cleanupInteractExecutions(t)

	t.Run("assign_via_interact_creates_execution_and_gets_host_reply", func(t *testing.T) {
		memberID := "robot_e2e_interact_assign"
		setupInteractRobot(t, memberID, "team_e2e_interact")

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

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

		robot, err := api.GetRobot(ctx, memberID)
		require.NoError(t, err)
		require.NotNil(t, robot)

		result, err := api.Interact(ctx, memberID, &api.InteractRequest{
			Source:  types.InteractSourceUI,
			Message: "Please write a short greeting email for our team meeting tomorrow morning.",
		})
		require.NoError(t, err)
		require.NotNil(t, result)

		t.Logf("Interact result: status=%s, message=%s, reply=%s, exec_id=%s, wait_for_more=%v",
			result.Status, result.Message, result.Reply, result.ExecutionID, result.WaitForMore)

		assert.NotEmpty(t, result.ExecutionID, "should create an execution")
		assert.NotEmpty(t, result.ChatID, "should have a chat session")
		assert.NotEmpty(t, result.Reply, "Host Agent should provide a reply")

		validStatuses := []string{"confirmed", "waiting_for_more", "adjusted", "acknowledged"}
		assert.Contains(t, validStatuses, result.Status,
			"status should be one of the valid Host Agent action outcomes")

		if result.Status == "confirmed" {
			time.Sleep(2 * time.Second)
			executions, err := api.ListExecutions(ctx, memberID, &api.ExecutionQuery{Page: 1, PageSize: 5})
			require.NoError(t, err)
			assert.Greater(t, len(executions.Data), 0, "confirmed execution should exist in store")
		}
	})
}

// TestE2EInteractStream tests the streaming version end-to-end.
func TestE2EInteractStream(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping E2E test - requires real LLM calls")
	}

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

	cleanupInteractRobots(t)
	cleanupInteractExecutions(t)
	defer cleanupInteractRobots(t)
	defer cleanupInteractExecutions(t)

	t.Run("stream_assign_returns_chunks_and_valid_result", func(t *testing.T) {
		memberID := "robot_e2e_interact_stream"
		setupInteractRobot(t, memberID, "team_e2e_interact")

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

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

		var mu sync.Mutex
		var chunks []*standard.StreamChunk

		streamFn := func(chunk *standard.StreamChunk) int {
			mu.Lock()
			defer mu.Unlock()
			chunks = append(chunks, chunk)
			return 0
		}

		result, err := api.InteractStream(ctx, memberID, &api.InteractRequest{
			Source:  types.InteractSourceUI,
			Message: "Help me draft a brief status update email about completing the Q4 report.",
		}, streamFn)
		require.NoError(t, err)
		require.NotNil(t, result)

		mu.Lock()
		chunkCount := len(chunks)
		var textChunks []string
		for _, c := range chunks {
			if c.Type == "text" && c.Delta {
				textChunks = append(textChunks, c.Content)
			}
		}
		mu.Unlock()

		combined := strings.Join(textChunks, "")

		t.Logf("Stream received %d total chunks, %d text chunks, combined length: %d",
			chunkCount, len(textChunks), len(combined))
		t.Logf("Result: status=%s, exec_id=%s, reply_len=%d, wait_for_more=%v",
			result.Status, result.ExecutionID, len(result.Reply), result.WaitForMore)

		assert.Greater(t, len(textChunks), 0, "should receive streaming text chunks from Host Agent")
		assert.NotEmpty(t, combined, "combined text should not be empty")
		assert.NotEmpty(t, result.ExecutionID, "should create an execution")
		assert.NotEmpty(t, result.Reply, "final result should contain reply")

		validStatuses := []string{"confirmed", "waiting_for_more", "adjusted"}
		assert.Contains(t, validStatuses, result.Status)
	})
}

// TestE2EInteractMultiTurn tests a multi-turn conversation:
// Turn 1: Send vague message -> Host Agent replies conversationally (waiting_for_more)
// Turn 2: Send clear confirmation -> Host Agent returns action JSON (confirmed or other action)
func TestE2EInteractMultiTurn(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping E2E test - requires real LLM calls")
	}

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

	cleanupInteractRobots(t)
	cleanupInteractExecutions(t)
	defer cleanupInteractRobots(t)
	defer cleanupInteractExecutions(t)

	t.Run("multi_turn_assign_conversation", func(t *testing.T) {
		memberID := "robot_e2e_interact_multiturn"
		setupInteractRobot(t, memberID, "team_e2e_interact")

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

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

		// Turn 1: Send vague message — expect conversational reply
		result1, err := api.Interact(ctx, memberID, &api.InteractRequest{
			Source:  types.InteractSourceUI,
			Message: "Do something with emails.",
		})
		require.NoError(t, err)
		require.NotNil(t, result1)

		t.Logf("Turn 1: status=%s, reply=%s, exec_id=%s, wait_for_more=%v",
			result1.Status, result1.Reply, result1.ExecutionID, result1.WaitForMore)

		assert.NotEmpty(t, result1.ExecutionID)
		assert.NotEmpty(t, result1.Reply)

		// Turn 2: Clarify/confirm with the same execution_id
		result2, err := api.Interact(ctx, memberID, &api.InteractRequest{
			ExecutionID: result1.ExecutionID,
			Source:      types.InteractSourceUI,
			Message:     "Yes, please write a brief thank-you email to the design team for their Q4 work. Go ahead and confirm.",
		})
		require.NoError(t, err)
		require.NotNil(t, result2)

		t.Logf("Turn 2: status=%s, reply=%s, exec_id=%s, wait_for_more=%v",
			result2.Status, result2.Reply, result2.ExecutionID, result2.WaitForMore)

		assert.NotEmpty(t, result2.Reply)
		assert.Equal(t, result1.ExecutionID, result2.ExecutionID, "should be same execution")

		validStatuses := []string{"confirmed", "waiting_for_more", "adjusted", "acknowledged"}
		assert.Contains(t, validStatuses, result2.Status,
			"second turn should produce a valid outcome")
	})
}

// TestE2EInteractStreamMultiTurn tests multi-turn with streaming.
func TestE2EInteractStreamMultiTurn(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping E2E test - requires real LLM calls")
	}

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

	cleanupInteractRobots(t)
	cleanupInteractExecutions(t)
	defer cleanupInteractRobots(t)
	defer cleanupInteractExecutions(t)

	t.Run("stream_multi_turn", func(t *testing.T) {
		memberID := "robot_e2e_interact_stream_mt"
		setupInteractRobot(t, memberID, "team_e2e_interact")

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

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

		// Turn 1
		var mu1 sync.Mutex
		var chunks1 []*standard.StreamChunk
		result1, err := api.InteractStream(ctx, memberID, &api.InteractRequest{
			Source:  types.InteractSourceUI,
			Message: "I need help with something.",
		}, func(chunk *standard.StreamChunk) int {
			mu1.Lock()
			chunks1 = append(chunks1, chunk)
			mu1.Unlock()
			return 0
		})
		require.NoError(t, err)
		require.NotNil(t, result1)

		mu1.Lock()
		t.Logf("Turn 1 stream: %d chunks, status=%s, reply=%s, wait_for_more=%v",
			len(chunks1), result1.Status, result1.Reply, result1.WaitForMore)
		mu1.Unlock()

		assert.NotEmpty(t, result1.ExecutionID)
		assert.NotEmpty(t, result1.Reply)

		// Turn 2: Clarify with same execution_id
		var mu2 sync.Mutex
		var chunks2 []*standard.StreamChunk
		result2, err := api.InteractStream(ctx, memberID, &api.InteractRequest{
			ExecutionID: result1.ExecutionID,
			Source:      types.InteractSourceUI,
			Message:     "Please compose a short farewell message for a colleague leaving the team. Yes, go ahead.",
		}, func(chunk *standard.StreamChunk) int {
			mu2.Lock()
			chunks2 = append(chunks2, chunk)
			mu2.Unlock()
			return 0
		})
		require.NoError(t, err)
		require.NotNil(t, result2)

		mu2.Lock()
		t.Logf("Turn 2 stream: %d chunks, status=%s, reply=%s, wait_for_more=%v",
			len(chunks2), result2.Status, result2.Reply, result2.WaitForMore)
		mu2.Unlock()

		assert.NotEmpty(t, result2.Reply)
		assert.Equal(t, result1.ExecutionID, result2.ExecutionID)
	})
}

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

func setupInteractRobot(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":   "Email Assistant",
			"duties": []string{"Write and manage emails"},
			"rules":  []string{"Always confirm before sending", "Keep emails professional"},
		},
		"quota": map[string]interface{}{
			"max":      5,
			"queue":    20,
			"priority": 5,
		},
		"triggers": map[string]interface{}{
			"intervene": map[string]interface{}{"enabled": true},
		},
		"resources": map[string]interface{}{
			"phases": map[string]interface{}{
				"inspiration": "robot.inspiration",
				"goals":       "robot.goals",
				"tasks":       "robot.tasks",
				"run":         "robot.validation",
				"validation":  "robot.validation",
				"delivery":    "robot.delivery",
				"learning":    "robot.learning",
				"host":        "robot.host",
			},
			"agents": []string{},
		},
	}
	configJSON, _ := json.Marshal(robotConfig)

	systemPrompt := `You are an email assistant for E2E testing of the Interact API.
When asked to write an email, confirm the task and generate a brief email draft.`

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

func cleanupInteractRobots(t *testing.T) {
	m := model.Select("__yao.member")
	if m == nil {
		return
	}
	qb := capsule.Query()
	_, err := qb.Table(m.MetaData.Table.Name).Where("member_id", "like", "robot_e2e_interact%").Delete()
	if err != nil {
		t.Logf("Warning: cleanup interact robots: %v", err)
	}
}

func cleanupInteractExecutions(t *testing.T) {
	m := model.Select("__yao.agent.execution")
	if m == nil {
		return
	}
	qb := capsule.Query()
	_, err := qb.Table(m.MetaData.Table.Name).Where("member_id", "like", "robot_e2e_interact%").Delete()
	if err != nil {
		t.Logf("Warning: cleanup interact executions: %v", err)
	}
}
