package run

import (
	"encoding/json"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

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

const (
	kernelSecretSQL        = "SELECT * FROM workflow_plan_versions WHERE tenant_id='acme'"
	kernelSecretCredential = "password=hunter2"
	kernelSecretToken      = "token=sk_live_abc123"
	kernelSecretStack      = "goroutine 42 [running]:"
)

func kernelSecretError() error {
	return &secretCauseError{message: kernelSecretSQL + " AND " + kernelSecretCredential + " " + kernelSecretToken + " " + kernelSecretStack}
}

type secretCauseError struct{ message string }

func (err *secretCauseError) Error() string { return err.message }

// TestKernelFailureWriterNeverPersistsArbitraryCause drives every durable kernel
// Failure writer (reducer activation failure, work-item evaluation failure, and
// fan-out closing join merge failure) with an arbitrary secret-bearing cause and
// proves the produced Failure never carries SQL/password/token/tenant/stack text
// and that a marshaled State carrying it is clean.
func TestKernelFailureWriterNeverPersistsArbitraryCause(t *testing.T) {
	t.Parallel()
	secrets := []string{kernelSecretSQL, kernelSecretCredential, kernelSecretToken, kernelSecretStack, "hunter2", "sk_live", "tenant_id"}

	// Reducer activation failure writer (routeRecoverableFailure path).
	reducerFailure := failureFromCause("message.target_invalid", "reducer", kernelSecretError())
	require.NotNil(t, reducerFailure)
	for _, secret := range secrets {
		require.NotContains(t, reducerFailure.Message, secret)
		require.NotContains(t, reducerFailure.Problem, secret)
		require.NotContains(t, reducerFailure.Hint, secret)
	}
	require.Equal(t, FailureProblem("message.target_invalid"), reducerFailure.Problem)
	require.Equal(t, FailureHint("message.target_invalid"), reducerFailure.Hint)

	// Work-item evaluation failure writer (fanout.go failFanOutItemEvaluation).
	itemFailure := failureFromCause("work_item.instruction_reference_invalid", "work_item", kernelSecretError())
	require.NotNil(t, itemFailure)
	for _, secret := range secrets {
		require.NotContains(t, itemFailure.Message, secret)
	}

	// Fan-out closing join merge failure writer (fanout_resolution.go).
	joinFailure := failureFromCause("join.merge_failed", "work_item", kernelSecretError())
	require.NotNil(t, joinFailure)
	for _, secret := range secrets {
		require.NotContains(t, joinFailure.Message, secret)
	}

	// A marshaled State carrying the failure is clean.
	state := &State{
		Status:         StatusFailed,
		Input:          Values{},
		Variables:      Values{},
		Activations:    []Activation{{ID: "a1", Failure: reducerFailure}},
		Scopes:         []Scope{},
		NextActivation: 1,
		Failure:        joinFailure,
	}
	encoded, err := json.Marshal(state)
	require.NoError(t, err)
	for _, secret := range secrets {
		require.NotContains(t, string(encoded), secret)
	}
	require.Contains(t, string(encoded), "message.target_invalid")
	require.Contains(t, string(encoded), "join.merge_failed")
}

// TestKernelFailureWriterPreservesStructuredDiagnostic verifies that when the
// cause is a structured ValidationError (bounded safe authoring text), the
// durable Failure carries the diagnostic verbatim for deterministic error
// routing, while still never carrying the raw arbitrary cause.
func TestKernelFailureWriterPreservesStructuredDiagnostic(t *testing.T) {
	t.Parallel()
	cause := validationErrorf(
		validationCodeDependencyTraversal,
		"fan_out.child.instruction",
		plan.WorkItemFieldInstruction,
		nil,
		"%s dependency %q output path %q does not exist",
		"fan_out.child.instruction",
		"parent",
		"answer",
	)
	failure := failureFromCause("work_item.instruction_reference_invalid", "work_item", cause)
	require.NotNil(t, failure)
	require.Contains(t, failure.Message, "does not exist")
	require.Contains(t, failure.Message, "parent")
	require.Len(t, failure.Diagnostics, 1)
	require.Equal(t, validationCodeDependencyTraversal, failure.Diagnostics[0].Code)
	require.NotContains(t, failure.Message, kernelSecretSQL)
	require.NotContains(t, failure.Message, "password=")
}

func TestFirstNonEmptyHelper(t *testing.T) {
	t.Parallel()
	require.Equal(t, "b", firstNonEmpty("", "b", "c"))
	require.Equal(t, "", firstNonEmpty("", "  ", ""))
	require.True(t, strings.Contains(FailureHint("message.delivery_failed"), "could not be delivered"))
}
