package rewindcaps

import (
	"testing"

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

func TestForRuntime(t *testing.T) {
	tests := []struct {
		runtime           string
		supported         bool
		strategy          string
		unsupportedReason string
	}{
		{runtime: RuntimeClaude, supported: true, strategy: StrategyClaudeSessionResumeAt},
		{runtime: RuntimeCodex, supported: true, strategy: StrategyCodexAppServerTurns},
		{runtime: RuntimeKimi, supported: true, strategy: StrategyKimiSessionTurns},
		{runtime: RuntimePi, supported: true, strategy: StrategyPiSessionTreeRewind},
		{runtime: RuntimeOpenCode, supported: true, strategy: StrategyOpenCodeSessionRevert},
		{runtime: RuntimeBuiltin, supported: true, strategy: StrategyOpenCodeSessionRevert},
		{runtime: "codex-app-server", supported: false, unsupportedReason: UnsupportedReasonUnknownRuntime},
		{runtime: "unknown", supported: false, unsupportedReason: UnsupportedReasonUnknownRuntime},
	}

	for _, tt := range tests {
		t.Run(tt.runtime, func(t *testing.T) {
			capability := ForRuntime(tt.runtime)
			require.Equal(t, tt.runtime, capability.Runtime)
			require.Equal(t, tt.supported, capability.Supported)
			require.Equal(t, tt.strategy, capability.Strategy)
			require.Equal(t, tt.unsupportedReason, capability.UnsupportedReason)
		})
	}
}

func TestSupportedRuntimes(t *testing.T) {
	require.Equal(t, []string{RuntimeClaude, RuntimeCodex, RuntimeKimi, RuntimeOpenCode, RuntimePi}, SupportedRuntimes())
}

func TestRuntimeCapabilityDoesNotTreatReservedStrategiesAsRuntimes(t *testing.T) {
	for _, strategy := range []string{StrategyClaudeSessionResumeAt, StrategyOpenCodeSessionRevert} {
		capability := ForRuntime(strategy)
		require.Equal(t, strategy, capability.Runtime)
		require.False(t, capability.Supported)
		require.Empty(t, capability.Strategy)
		require.Equal(t, UnsupportedReasonUnknownRuntime, capability.UnsupportedReason)
	}
}

func TestCodexAppServerRecoveryRefRoundTrip(t *testing.T) {
	recoveryRef, err := EncodeCodexAppServerRecoveryRef(" thread-123 ", 2)
	require.NoError(t, err)

	ref, err := DecodeCodexAppServerRecoveryRef(recoveryRef)
	require.NoError(t, err)
	require.Equal(t, "thread-123", ref.ThreadID)
	require.Equal(t, 2, ref.TurnCount)
}

func TestClaudeSessionResumeAtRecoveryRefRoundTrip(t *testing.T) {
	recoveryRef, err := EncodeClaudeSessionResumeAtRecoveryRef(" claude-session-1 ", " boundary-1 ", "")
	require.NoError(t, err)

	ref, err := DecodeClaudeSessionResumeAtRecoveryRef(recoveryRef)
	require.NoError(t, err)
	require.Equal(t, "claude-session-1", ref.SessionID)
	require.Equal(t, "boundary-1", ref.BoundaryUUID)
	require.Equal(t, RecoveryBoundaryResumeAt, ref.BoundaryKind)

	recoveryRef, err = EncodeClaudeSessionResumeAtRecoveryRef("claude-session-1", "", RecoveryBoundaryBeforeFirstInput)
	require.NoError(t, err)
	ref, err = DecodeClaudeSessionResumeAtRecoveryRef(recoveryRef)
	require.NoError(t, err)
	require.Equal(t, RecoveryBoundaryBeforeFirstInput, ref.BoundaryKind)
	require.Empty(t, ref.BoundaryUUID)
}

func TestOpenCodeSessionRevertRecoveryRefRoundTrip(t *testing.T) {
	recoveryRef, err := EncodeOpenCodeSessionRevertRecoveryRef(" sess-1 ", " msg_123 ")
	require.NoError(t, err)

	ref, err := DecodeOpenCodeSessionRevertRecoveryRef(recoveryRef)
	require.NoError(t, err)
	require.Equal(t, "sess-1", ref.SessionID)
	require.Equal(t, "msg_123", ref.MessageID)
}

func TestKimiSessionTurnsRecoveryRefRoundTrip(t *testing.T) {
	recoveryRef, err := EncodeKimiSessionTurnsRecoveryRef(" sess-1 ", 3)
	require.NoError(t, err)

	ref, err := DecodeKimiSessionTurnsRecoveryRef(recoveryRef)
	require.NoError(t, err)
	require.Equal(t, "sess-1", ref.SessionID)
	require.Equal(t, 3, ref.TurnCount)
	require.Equal(t, 3, ref.InputCount)
}

func TestValidatePreparedRecoveryPoint(t *testing.T) {
	recoveryRef, err := EncodeCodexAppServerRecoveryRef("thread-123", 0)
	require.NoError(t, err)
	require.NoError(t, ValidatePreparedRecoveryPoint(StrategyCodexAppServerTurns, "thread-123", recoveryRef))

	recoveryRef, err = EncodeOpenCodeSessionRevertRecoveryRef("sess-1", "msg_abc")
	require.NoError(t, err)
	require.NoError(t, ValidatePreparedRecoveryPoint(StrategyOpenCodeSessionRevert, "sess-1", recoveryRef))

	recoveryRef, err = EncodeKimiSessionTurnsRecoveryRef("kimi-session-1", 0)
	require.NoError(t, err)
	require.NoError(t, ValidatePreparedRecoveryPoint(StrategyKimiSessionTurns, "kimi-session-1", recoveryRef))

	recoveryRef, err = EncodeClaudeSessionResumeAtRecoveryRef("claude-session-1", "uuid-1", RecoveryBoundaryResumeAt)
	require.NoError(t, err)
	require.NoError(t, ValidatePreparedRecoveryPoint(StrategyClaudeSessionResumeAt, "claude-session-1", recoveryRef))

	recoveryRef, err = EncodeClaudeSessionResumeAtRecoveryRef("claude-session-1", "", RecoveryBoundaryBeforeFirstInput)
	require.NoError(t, err)
	require.NoError(t, ValidatePreparedRecoveryPoint(StrategyClaudeSessionResumeAt, "claude-session-1", recoveryRef))

	err = ValidatePreparedRecoveryPoint(StrategyCodexAppServerTurns, "thread-456", `{"threadId":"thread-123","turnCount":2}`)
	require.ErrorContains(t, err, "codex recovery ref")
	require.ErrorContains(t, err, `does not match thread id "thread-456"`)
	require.NotContains(t, err.Error(), "codex-app-server")

	err = ValidatePreparedRecoveryPoint(StrategyCodexAppServerTurns, "thread-123", `{"threadId":"thread-123","turnCount":-1}`)
	require.ErrorContains(t, err, "invalid turn count")

	err = ValidatePreparedRecoveryPoint(StrategyOpenCodeSessionRevert, "sess-1", `{"sessionId":"sess-1","messageId":"bad"}`)
	require.ErrorContains(t, err, "does not start with msg")

	err = ValidatePreparedRecoveryPoint(StrategyKimiSessionTurns, "kimi-session-1", `{"sessionId":"kimi-session-1","turnCount":-1}`)
	require.ErrorContains(t, err, "invalid turn count")

	err = ValidatePreparedRecoveryPoint(StrategyKimiSessionTurns, "other-session", `{"sessionId":"kimi-session-1","turnCount":0}`)
	require.ErrorContains(t, err, "does not match thread id")

	err = ValidatePreparedRecoveryPoint(StrategyClaudeSessionResumeAt, "claude-session-1", `{"sessionId":"claude-session-1","boundaryKind":"resume_at"}`)
	require.ErrorContains(t, err, "missing boundary uuid")

	err = ValidatePreparedRecoveryPoint(StrategyClaudeSessionResumeAt, "claude-session-1", `{"sessionId":"claude-session-1","boundaryKind":"bogus"}`)
	require.ErrorContains(t, err, "unknown boundary kind")

	err = ValidatePreparedRecoveryPoint("builtin_message_count", "builtin-session-1", `{"sessionId":"builtin-session-1","userMessageCount":2}`)
	require.ErrorContains(t, err, `unknown recovery strategy "builtin_message_count"`)
}
