package agentstate

import (
	"errors"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestAdvanceWakeDeliveryAllowsPaths(t *testing.T) {
	received := transitionWake(WakePhaseReceived)

	confirmed := transitionWake(WakePhaseReplyConfirmed)
	confirmed.ReplyObjectIDs = []string{"reply-1"}
	got, err := AdvanceWakeDelivery(received, confirmed)
	require.NoError(t, err)
	require.Equal(t, WakePhaseReplyConfirmed, got.Phase)

	received = transitionWake(WakePhaseReceived)
	noReply := transitionWake(WakePhaseNoReply)
	noReply.NoReplyReasonCode = WakeNoReplyDecided
	got, err = AdvanceWakeDelivery(received, noReply)
	require.NoError(t, err)
	require.Equal(t, WakePhaseNoReply, got.Phase)

	received = transitionWake(WakePhaseReceived)
	failed := transitionWake(WakePhaseFailed)
	failed.FailureReasonCode = WakeFailureRuntimeFailed
	got, err = AdvanceWakeDelivery(received, failed)
	require.NoError(t, err)
	require.Equal(t, WakePhaseFailed, got.Phase)
}

func TestAdvanceWakeDeliveryTerminalStatesAreImmutable(t *testing.T) {
	confirmed := transitionWake(WakePhaseReplyConfirmed)
	confirmed.ReplyObjectIDs = []string{"reply-1"}
	failed := transitionWake(WakePhaseFailed)
	failed.FailureReasonCode = WakeFailureRuntimeFailed

	_, err := AdvanceWakeDelivery(confirmed, failed)
	require.ErrorIs(t, err, ErrTerminalWake)
}

func TestAdvanceWakeDeliveryRejectsIdentityMutation(t *testing.T) {
	current := transitionWake(WakePhaseReceived)
	next := current
	next.TargetKey = "#other"

	_, err := AdvanceWakeDelivery(current, next)
	require.ErrorIs(t, err, ErrWakeIdentityChanged)
	require.True(t, errors.Is(err, ErrInvalidTransition))
}

func TestAdvanceWakeDeliveryRejectsInvalidIntermediatePhase(t *testing.T) {
	current := transitionWake(WakePhaseReceived)
	next := current
	next.Phase = WakeDeliveryPhase("obsolete_intermediate")

	_, err := AdvanceWakeDelivery(current, next)
	require.Error(t, err)
}

func TestValidateExecutionTransitionAllowsPaths(t *testing.T) {
	idle := transitionExecution(ExecutionIdle)
	running := transitionExecution(ExecutionRunning)
	running.RunningKind = RunningThinking
	running.AttemptToken = "attempt-1"
	retry := transitionExecution(ExecutionRetryWait)
	retry.ReasonCode = ExecutionReasonRateLimited
	retry.AttemptToken = running.AttemptToken
	recovered := running
	recovered.RunningKind = RunningWorking
	recovered.ChangedAt = recovered.ChangedAt.Add(time.Second)
	recovering := transitionExecution(ExecutionRecovering)
	recovering.ReasonCode = ExecutionReasonProcessInterrupted
	recovering.AttemptToken = running.AttemptToken
	failed := transitionExecution(ExecutionFailed)
	failed.ReasonCode = ExecutionReasonRecoveryBudgetExhausted
	failed.AttemptToken = running.AttemptToken

	require.NoError(t, ValidateExecutionTransition(idle, running))
	require.NoError(t, ValidateExecutionTransition(running, retry))
	require.NoError(t, ValidateExecutionTransition(retry, recovered))
	require.NoError(t, ValidateExecutionTransition(recovered, recovering))
	require.NoError(t, ValidateExecutionTransition(recovering, recovered))
	require.NoError(t, ValidateExecutionTransition(recovering, failed))
}

func TestExecutionMayIdleWithBackgroundReceiptReconcile(t *testing.T) {
	current := transitionExecution(ExecutionRunning)
	current.RunningKind = RunningWorking
	current.AttemptToken = "attempt-1"
	next := transitionExecution(ExecutionIdle)

	require.NoError(t, ValidateExecutionTransition(current, next))
}

func TestValidateExecutionTransitionRejectsIdleToRetryAndAttemptReplacement(t *testing.T) {
	idle := transitionExecution(ExecutionIdle)
	retry := transitionExecution(ExecutionRetryWait)
	retry.ReasonCode = ExecutionReasonRateLimited
	retry.AttemptToken = "attempt-1"

	require.ErrorIs(t, ValidateExecutionTransition(idle, retry), ErrInvalidTransition)

	running := transitionExecution(ExecutionRunning)
	running.RunningKind = RunningThinking
	running.AttemptToken = "attempt-1"
	retry.AttemptToken = "attempt-2"
	require.ErrorIs(t, ValidateExecutionTransition(running, retry), ErrAttemptIdentityChanged)
}

func transitionWake(phase WakeDeliveryPhase) WakeDeliveryState {
	now := time.Date(2026, 8, 19, 2, 0, 0, 0, time.UTC)
	return WakeDeliveryState{
		LocalWakeID:     "wake-1",
		SourceMessageID: "msg-1",
		TargetKey:       "#ops",
		AckedAt:         now,
		ChangedAt:       now.Add(time.Second),
		Phase:           phase,
	}
}

func transitionExecution(phase ExecutionPhase) AgentExecutionState {
	return AgentExecutionState{
		Phase:     phase,
		ChangedAt: time.Date(2026, 8, 19, 2, 0, 0, 0, time.UTC),
	}
}
