package run

import (
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/workflow/plan"
)

func TestForkRoutingBindingFailureIsAtomic(t *testing.T) {
	t.Parallel()
	for _, failingEdgeID := range []string{"left", "right"} {
		t.Run(failingEdgeID, func(t *testing.T) {
			kernel, graph := goldenKernel(t, "fork_join_all.json")
			edge, ok := graph.Edge(failingEdgeID)
			require.True(t, ok)
			require.Len(t, edge.Bindings, 1)
			require.NotNil(t, edge.Bindings[0].Value.Load)
			// Simulate a compiler-approved dynamic binding that cannot be
			// resolved for this activation. Mutating the indexed test artifact
			// keeps the test focused on reducer transactionality rather than a
			// particular source-level optional-value policy.
			edge.Bindings[0].Value.Load.Path = []string{"missing"}

			state, err := kernel.Start(Values{"value": "answer"})
			require.NoError(t, err)
			transition, err := kernel.Step(state, reducerNow)
			require.NoError(t, err)
			require.Equal(t, StatusFailed, transition.State.Status)
			require.Equal(t, "edge.binding_failed", transition.State.Failure.Code)
			require.Empty(t, transition.Intents, "discard the join timeout prepared before a sibling route failed")
			require.Empty(t, transition.State.Scopes, "discard the partially opened parallel scope")
			require.Len(t, transition.State.Activations, 1, "discard every partially prepared sibling/join activation")
			require.Equal(t, ActivationFailed, transition.State.Activations[0].Status)
			for _, activation := range transition.State.Activations {
				require.NotContains(t, []ActivationStatus{ActivationReady, ActivationWaiting}, activation.Status)
			}
		})
	}
}

func TestNestedLoopBoundaryTransitionDoesNotDependOnOwnerIDOrder(t *testing.T) {
	t.Parallel()
	kernel := compileRunKernel(t, nestedLoopOwnerOrderPlan)
	state, err := kernel.Start(Values{"answer": "again"})
	require.NoError(t, err)
	for step := 0; step < 20 && !state.Status.Terminal(); step++ {
		transition, stepErr := kernel.Step(state, reducerNow.Add(time.Duration(step)*time.Second))
		require.NoError(t, stepErr)
		state = transition.State
	}
	require.Equal(t, StatusSucceeded, state.Status)
	require.Equal(t, "again", state.Output["answer"])
	require.Len(t, state.Scopes, 2)
	require.Equal(t, ScopeSucceeded, state.Scopes[0].Status)
	require.Equal(t, ScopeSucceeded, state.Scopes[1].Status)
}

func compileRunKernel(t *testing.T, source string) *Kernel {
	t.Helper()
	artifact, diagnostics, err := plan.Compile([]byte(source))
	require.NoError(t, err)
	require.False(t, plan.HasErrors(diagnostics), "diagnostics: %#v", diagnostics)
	graph, err := IndexArtifact(artifact)
	require.NoError(t, err)
	kernel, err := NewKernel(graph)
	require.NoError(t, err)
	return kernel
}

const nestedLoopOwnerOrderPlan = `apiVersion: loop.plan/v1
kind: PlanVersion
input:
  answer: {type: string, required: true}
output:
  answer: {type: string, required: true}
graph:
  entry:
    nodeId: a_outer
    port: enter
    input: {answer: {ref: plan.input.answer}}
  nodes:
  - id: a_outer
    type: loop
    loop:
      mode: counted
      iterations: 1
      state: {answer: {type: string, required: true}}
  - id: z_inner
    type: loop
    loop:
      mode: counted
      iterations: 1
      state: {answer: {type: string, required: true}}
  - id: route
    type: switch
    input: {answer: {type: string, required: true}}
    switch:
      mode: first_match
      cases:
      - {port: again, when: 'input.answer == "again"'}
      - {port: stop, isDefault: true}
  - id: success
    type: end
    end: {outcome: succeeded}
  - id: failure
    type: end
    end: {outcome: failed, code: loop_failed}
  edges:
  - id: outer_body
    source: {nodeId: a_outer, port: body}
    target: {nodeId: z_inner, port: enter}
    bindings: {answer: {ref: loop.state.answer}}
  - id: inner_body
    source: {nodeId: z_inner, port: body}
    target: {nodeId: route, port: in}
    bindings: {answer: {ref: loop.state.answer}}
  - id: inner_again
    source: {nodeId: route, port: again}
    target: {nodeId: z_inner, port: continue}
    bindings: {answer: {ref: input.answer}}
  - id: inner_stop
    source: {nodeId: route, port: stop}
    target: {nodeId: z_inner, port: break}
    bindings: {answer: {ref: input.answer}}
  - id: inner_done_to_outer_continue
    source: {nodeId: z_inner, port: done}
    target: {nodeId: a_outer, port: continue}
    bindings: {answer: {ref: loop.state.answer}}
  - id: inner_failed_to_outer_break
    source: {nodeId: z_inner, port: failed}
    target: {nodeId: a_outer, port: break}
    bindings: {answer: {ref: plan.input.answer}}
  - id: outer_done
    source: {nodeId: a_outer, port: done}
    target: {nodeId: success, port: in}
    bindings: {answer: {ref: loop.state.answer}}
  - id: outer_failed
    source: {nodeId: a_outer, port: failed}
    target: {nodeId: failure, port: in}
`
