package run

import (
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/workflow/plan"
)

func TestEvalMessageCollaborationScopesNormalizesAndDeduplicatesRelatedScopes(t *testing.T) {
	t.Parallel()

	work := scopeExpression(`{"kind":"task","scopeId":"tsk_work"}`)
	requirements := scopeExpression(`{"kind":"thread","scopeId":"thr_requirements"}`)
	design := scopeExpression(`{"kind":"channel","scopeId":"chn_design"}`)
	config := &plan.ArtifactSendMessageConfig{
		WorkScopeMode: plan.ArtifactWorkScopeFixed,
		WorkScope:     &work,
		RelatedScopes: []plan.ArtifactExpression{
			requirements,
			design,
			requirements,
			design,
		},
	}

	mode, workScope, relatedScopes, err := evalMessageCollaborationScopes(config, EvalContext{})
	require.NoError(t, err)
	require.Equal(t, WorkScopeModeFixed, mode)
	require.Equal(t, &domain.MessageScopeValue{
		Kind: domain.MessageScopeKindTask, ScopeID: "tsk_work",
	}, workScope)
	require.Equal(t, []domain.MessageScopeValue{
		{Kind: domain.MessageScopeKindThread, ScopeID: "thr_requirements"},
		{Kind: domain.MessageScopeKindChannel, ScopeID: "chn_design"},
	}, relatedScopes)
}

func TestCompiledWorkScopesFreezeIntoSendMessageIntent(t *testing.T) {
	t.Parallel()
	t.Run("fixed scope and related scopes", func(t *testing.T) {
		kernel := compileRunKernel(t, fixedWorkScopeRuntimePlan)
		state, err := kernel.Start(Values{})
		require.NoError(t, err)

		transition, err := kernel.Step(state, reducerNow)
		require.NoError(t, err)
		require.Len(t, transition.Intents, 1)
		intent := transition.Intents[0]
		require.Equal(t, IntentSendMessage, intent.Kind)
		require.True(t, intent.Callback)
		require.Equal(t, WorkScopeModeFixed, intent.WorkScopeMode)
		require.Equal(t, &domain.MessageScopeValue{
			Kind: domain.MessageScopeKindTask, ScopeID: "tsk_abcdefghijklmn",
		}, intent.WorkScope)
		require.Equal(t, []domain.MessageScopeValue{
			{Kind: domain.MessageScopeKindThread, ScopeID: "thr_abcdefghijklmn"},
			{Kind: domain.MessageScopeKindChannel, ScopeID: "chn_abcdefghijklmn"},
		}, intent.RelatedScopes, "freeze normalized scopes and retain each first occurrence")
	})

	t.Run("agent selected scope", func(t *testing.T) {
		kernel := compileRunKernel(t, agentWorkScopeRuntimePlan)
		state, err := kernel.Start(Values{})
		require.NoError(t, err)

		transition, err := kernel.Step(state, reducerNow)
		require.NoError(t, err)
		require.Len(t, transition.Intents, 1)
		intent := transition.Intents[0]
		require.Equal(t, WorkScopeModeAgent, intent.WorkScopeMode)
		require.Nil(t, intent.WorkScope)
		require.Empty(t, intent.RelatedScopes)
	})
}

func TestStaticFixedTaskCallbackWatchesResponderWithoutInferredPredecessors(t *testing.T) {
	t.Parallel()

	kernel := compileRunKernel(t, staticFixedTaskCallbackRuntimePlan)
	taskScope := map[string]any{
		"kind":    string(domain.MessageScopeKindTask),
		"scopeId": "tsk_static_callback",
	}
	state, err := kernel.Start(Values{
		"work_task": taskScope,
		"reviewer":  "@reviewer",
	})
	require.NoError(t, err)

	transition, err := kernel.Step(state, reducerNow)
	require.NoError(t, err)
	require.Len(t, transition.Intents, 1)
	intent := transition.Intents[0]
	require.True(t, intent.Callback)
	require.Equal(t, "@reviewer", intent.Responder)
	require.Equal(t, intent.Scope, intent.WorkScope)
	require.NotNil(t, intent.TaskWatchHandoff)
	require.Equal(t, domain.MessageScopeValue{
		Kind: domain.MessageScopeKindTask, ScopeID: "tsk_static_callback",
	}, intent.TaskWatchHandoff.TaskScope)
	require.Equal(
		t,
		intent.ActivationID,
		intent.TaskWatchHandoff.WorkItemID,
		"a static callback uses its stable activation ID as the handoff source identity",
	)
	require.NotNil(t, intent.TaskWatchHandoff.CausalPredecessorAgentIDs)
	require.Empty(t, intent.TaskWatchHandoff.CausalPredecessorAgentIDs)
}

func scopeExpression(literal string) plan.ArtifactExpression {
	return plan.ArtifactExpression{
		Op:         plan.ArtifactOpConst,
		ResultType: plan.TypeScope,
		Value:      plan.ArtifactLiteral(literal),
	}
}

const fixedWorkScopeRuntimePlan = `apiVersion: loop.plan/v1
kind: PlanVersion
state:
  implementation:
    type: scope
    default: {kind: task, scopeId: tsk_abcdefghijklmn}
  requirement:
    type: scope
    default: {kind: thread, scopeId: thr_abcdefghijklmn}
  design:
    type: scope
    default: {kind: channel, scopeId: chn_abcdefghijklmn}
graph:
  entry: {nodeId: implement, port: in}
  nodes:
  - id: implement
    type: send_message
    sendMessage:
      target: "#all"
      text: implement the requested change
      completion: callback
      workScope: {ref: state.implementation}
      relatedScopes:
      - {ref: state.requirement}
      - {ref: state.design}
      - {ref: state.requirement}
  - id: success
    type: end
    end: {outcome: succeeded}
  edges:
  - id: implemented
    source: {nodeId: implement, port: completed}
    target: {nodeId: success, port: in}
`

const agentWorkScopeRuntimePlan = `apiVersion: loop.plan/v1
kind: PlanVersion
graph:
  entry: {nodeId: implement, port: in}
  nodes:
  - id: implement
    type: send_message
    sendMessage:
      target: "#all"
      text: implement the requested change
      completion: callback
      workScope: null
      relatedScopes: []
  - id: success
    type: end
    end: {outcome: succeeded}
  edges:
  - id: implemented
    source: {nodeId: implement, port: completed}
    target: {nodeId: success, port: in}
`

const staticFixedTaskCallbackRuntimePlan = `apiVersion: loop.plan/v1
kind: PlanVersion
input:
  work_task: {type: scope, required: true}
  reviewer: {type: agent, required: true}
graph:
  entry:
    nodeId: review
    port: in
    input:
      work_task: {ref: plan.input.work_task}
      reviewer: {ref: plan.input.reviewer}
  nodes:
  - id: review
    type: send_message
    input:
      work_task: {type: scope, required: true}
      reviewer: {type: agent, required: true}
    sendMessage:
      scope: {ref: input.work_task}
      responder: {ref: input.reviewer}
      text: review the result
      completion: callback
      workScope: {ref: input.work_task}
      callbackOutput:
        approved: {type: boolean, required: true}
  - id: success
    type: end
    end: {outcome: succeeded}
  edges:
  - id: reviewed
    source: {nodeId: review, port: completed}
    target: {nodeId: success, port: in}
`
