package agent

import (
	"sync"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/drivercontract"
	"github.com/tidbcloud/AgentTeam/desktop/internal/consts"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

func TestWorkflowCompactionGateAwaitingSurvivesOrdinaryObservations(t *testing.T) {
	gate := NewWorkflowCompactionGate()
	gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-a", "run-a", "step-a"))

	require.False(t, gate.allowProactiveCompactionCheck())
	gate.ObserveRuntimeMessage(types.InboxMessage{MessageID: "ordinary-a"})
	require.False(t, gate.allowProactiveCompactionCheck())

	gate.WorkflowStepCompleted("run-a", "step-a")
	require.True(t, gate.allowProactiveCompactionCheck())
}

func TestWorkflowCompactionGateDuplicateObservationPreservesTerminalOutcome(t *testing.T) {
	gate := NewWorkflowCompactionGate()
	message := workflowCompactionMessage("workflow-a", "run-a", "step-a")
	gate.ObserveRuntimeMessage(message)
	gate.WorkflowStepCompleted("run-a", "step-a")

	gate.ObserveRuntimeMessage(message)

	require.True(t, gate.allowProactiveCompactionCheck())
}

func TestWorkflowCompactionGateUnsuccessfulTerminalSkipsOnlyItsBoundary(t *testing.T) {
	gate := NewWorkflowCompactionGate()
	gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-a", "run-a", "step-a"))
	gate.WorkflowStepUnsuccessful("run-a", "step-a")

	require.False(t, gate.allowProactiveCompactionCheck())
	require.True(t, gate.allowProactiveCompactionCheck(),
		"an independent later turn must restore ordinary proactive-compaction checks")
}

func TestWorkflowCompactionGateOrdinaryObservationClearsUnsuccessfulTerminal(t *testing.T) {
	gate := NewWorkflowCompactionGate()
	gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-a", "run-a", "step-a"))
	gate.WorkflowStepUnsuccessful("run-a", "step-a")

	gate.ObserveRuntimeMessage(types.InboxMessage{MessageID: "ordinary-a"})

	require.True(t, gate.allowProactiveCompactionCheck(),
		"ordinary work delivered before the failed Step boundary becomes the next independent turn")
}

func TestWorkflowCompactionGateRecoveredObservationOnlyRestoresWorkflowStep(t *testing.T) {
	gate := NewWorkflowCompactionGate()
	gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-a", "run-a", "step-a"))
	gate.WorkflowStepUnsuccessful("run-a", "step-a")

	gate.observeWorkflowRuntimeMessage(types.InboxMessage{MessageID: "old-ordinary"})
	require.False(t, gate.allowProactiveCompactionCheck(),
		"a recovered old observation is not an independent new turn")
	require.True(t, gate.allowProactiveCompactionCheck())

	gate.observeWorkflowRuntimeMessage(workflowCompactionMessage("workflow-b", "run-b", "step-b"))
	require.False(t, gate.allowProactiveCompactionCheck(),
		"recovered authoritative acknowledgement must restore Workflow suppression")
}

func TestWorkflowCompactionGateLateTerminalCannotClearNewerStep(t *testing.T) {
	gate := NewWorkflowCompactionGate()
	gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-a", "run-a", "step-a"))
	gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-b", "run-b", "step-b"))

	gate.WorkflowStepCompleted("run-a", "step-a")
	gate.WorkflowStepUnsuccessful("run-a", "step-a")
	gate.WorkflowRunCanceled("run-a")

	require.False(t, gate.allowProactiveCompactionCheck())
	gate.WorkflowStepCompleted("run-b", "step-b")
	require.True(t, gate.allowProactiveCompactionCheck())
}

func TestWorkflowCompactionGateConcurrentNewStepWinsLateCompletion(t *testing.T) {
	for range 100 {
		gate := NewWorkflowCompactionGate()
		gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-a", "run-a", "step-a"))

		start := make(chan struct{})
		var group sync.WaitGroup
		group.Add(2)
		go func() {
			defer group.Done()
			<-start
			gate.WorkflowStepCompleted("run-a", "step-a")
		}()
		go func() {
			defer group.Done()
			<-start
			gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-b", "run-b", "step-b"))
		}()
		close(start)
		group.Wait()

		require.False(t, gate.allowProactiveCompactionCheck())
		gate.WorkflowStepCompleted("run-b", "step-b")
		require.True(t, gate.allowProactiveCompactionCheck())
	}
}

func TestHandleParsedEventTurnEndSuppressesProactiveCompactionWhileWorkflowAwaits(t *testing.T) {
	driver := &proactiveCompactionStubDriver{started: true}
	gate := NewWorkflowCompactionGate()
	ap := newTestProcessForRecovery("agent-workflow-awaiting", "sess-1", driver)
	ap.workflowCompaction = gate
	gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-a", "run-a", "step-a"))

	ap.handleParsedEvent(drivercontract.ParsedEvent{Kind: consts.ParsedEventKindTurnEnd})
	require.Zero(t, driver.calls)

	gate.ObserveRuntimeMessage(types.InboxMessage{MessageID: "ordinary-a"})
	ap.handleParsedEvent(drivercontract.ParsedEvent{Kind: consts.ParsedEventKindTurnEnd})
	require.Zero(t, driver.calls, "interleaved ordinary turns remain inside the awaiting Workflow Step")

	gate.WorkflowStepCompleted("run-a", "step-a")
	ap.handleParsedEvent(drivercontract.ParsedEvent{Kind: consts.ParsedEventKindTurnEnd})
	require.Equal(t, 1, driver.calls)
}

func TestHandleParsedEventTurnEndSkipsUnsuccessfulWorkflowBoundaryThenRestoresOrdinaryChecks(t *testing.T) {
	for _, test := range []struct {
		name      string
		terminate func(*WorkflowCompactionGate)
	}{
		{
			name: "fail",
			terminate: func(gate *WorkflowCompactionGate) {
				gate.WorkflowStepUnsuccessful("run-a", "step-a")
			},
		},
		{
			name: "cancel",
			terminate: func(gate *WorkflowCompactionGate) {
				gate.WorkflowRunCanceled("run-a")
			},
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			driver := &proactiveCompactionStubDriver{started: true}
			gate := NewWorkflowCompactionGate()
			ap := newTestProcessForRecovery("agent-workflow-"+test.name, "sess-1", driver)
			ap.workflowCompaction = gate
			gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-a", "run-a", "step-a"))
			test.terminate(gate)

			ap.handleParsedEvent(drivercontract.ParsedEvent{Kind: consts.ParsedEventKindTurnEnd})
			require.Zero(t, driver.calls)

			gate.ObserveRuntimeMessage(types.InboxMessage{MessageID: "ordinary-a"})
			ap.handleParsedEvent(drivercontract.ParsedEvent{Kind: consts.ParsedEventKindTurnEnd})
			require.Equal(t, 1, driver.calls)
		})
	}
}

func TestHandleParsedEventTurnEndRunsFinalCheckBeforeCompletedWorkflowCompaction(t *testing.T) {
	driver := &proactiveCompactionDeliveryStubDriver{
		proactiveCompactionStubDriver: proactiveCompactionStubDriver{started: true},
	}
	gate := NewWorkflowCompactionGate()
	ap := newTestProcessForRecovery("agent-workflow-final-check", "sess-1", driver)
	ap.workflowCompaction = gate
	ap.mu.Lock()
	_, started := ap.beginWakeDeliveryForMessageLocked(types.InboxMessage{
		MessageID: "workflow-a",
		Target:    "#loop:00000014",
	}, "received")
	ap.mu.Unlock()
	require.True(t, started)
	gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-a", "run-a", "step-a"))
	gate.WorkflowStepCompleted("run-a", "step-a")

	ap.handleParsedEvent(drivercontract.ParsedEvent{Kind: consts.ParsedEventKindTurnEnd})
	require.Len(t, driver.inputs, 1)
	require.Zero(t, driver.calls)

	ap.handleParsedEvent(drivercontract.ParsedEvent{Kind: consts.ParsedEventKindTurnEnd})
	require.Equal(t, 1, driver.calls)
}

func TestHandleParsedEventTurnEndDeliversNextWorkflowBeforeCompletedWorkflowCompaction(t *testing.T) {
	driver := &proactiveCompactionDeliveryStubDriver{
		proactiveCompactionStubDriver: proactiveCompactionStubDriver{started: true},
	}
	gate := NewWorkflowCompactionGate()
	ap := newTestProcessForRecovery("agent-workflow-next-step", "sess-1", driver)
	ap.workflowCompaction = gate
	gate.ObserveRuntimeMessage(workflowCompactionMessage("workflow-a", "run-a", "step-a"))
	gate.WorkflowStepCompleted("run-a", "step-a")

	next := workflowCompactionMessage("workflow-b", "run-b", "step-b")
	next.Target = "#loop:00000014"
	next.ChannelType = "channel"
	next.ChannelName = "loop"
	next.SenderType = "workflow"
	next.SenderName = "workflow"
	next.Content = "next workflow step"
	ap.inbox = []types.InboxMessage{next}

	ap.handleParsedEvent(drivercontract.ParsedEvent{Kind: consts.ParsedEventKindTurnEnd})

	require.Len(t, driver.inputs, 1)
	require.Equal(t, "workflow-b", driver.inputs[0].ID)
	require.Zero(t, driver.calls)
	ap.handleParsedEvent(drivercontract.ParsedEvent{Kind: consts.ParsedEventKindTurnEnd})
	require.Zero(t, driver.calls, "the newly visible Step must replace the prior completion permission")
}

func workflowCompactionMessage(messageID, runID, stepID string) types.InboxMessage {
	return types.InboxMessage{
		MessageID: messageID,
		WorkflowContext: types.WorkflowContext{
			WorkspaceID:      "workspace-a",
			RunID:            runID,
			EffectID:         stepID,
			NodeID:           "node-a",
			CallbackExpected: true,
		},
	}
}
