package run

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	store "github.com/tidbcloud/AgentTeam/internal/store/workflow/run"
)

// ---------------------------------------------------------------------------
// Public projection boundary fail-closed regressions (#t2747).
//
// The public RunLog/Event projection boundary validates the ORIGINAL persisted
// event-kind bytes byte-for-byte before any discriminator-derived top-level
// field (Kind/Type/Level/Message) is filled. A padded, invalid-UTF-8,
// token-bearing, or unknown kind fails closed: no malformed byte reaches those
// fields, and the payload still projects to the scrubbed empty object. The
// typed event / EffectResult / operational projectors fail closed for a
// scalar/array/null top-level value. User-authored Run input/output/state is
// preserved exactly (cloneJSON / safeDurableJSON untouched).
// ---------------------------------------------------------------------------

func sampleEvent(kind string, payload string, now time.Time) store.Event {
	return store.Event{
		ID: "wre_1", RunID: "wri_1", Seq: 1, Type: kind,
		PayloadJSON: json.RawMessage(payload),
		CreatedAt:   now,
	}
}

// TestRunLogDiscriminatorFailClosesOnMalformedKindBytes proves a padded,
// invalid-UTF-8, token-bearing, or unknown persisted event kind NEVER reaches
// the top-level Kind/Level/Message fields of the RunLog DTO.
func TestRunLogDiscriminatorFailClosesOnMalformedKindBytes(t *testing.T) {
	now := time.Now().UTC()
	payload := `{"activationId":"a00000000000000000001","nodeId":"wnd_1","kind":"send_message","code":"message.delivery_failed","secret":"hunter2"}`
	for _, kind := range []string{
		" run_failed ",
		"\trun_failed\n",
		"run_failed\xff",
		"run_failed=SECRET",
		"run_failed token",
		"future_unknown_kind",
		"",
	} {
		log := RunLogResponse(sampleEvent(kind, payload, now))
		require.Equal(t, "unknown", log.Kind, "malformed kind %q must fail closed to the neutral Kind", kind)
		require.Equal(t, "info", log.Level, "malformed kind %q must fail closed to info Level", kind)
		require.Equal(t, "Workflow progressed", log.Message, "malformed kind %q must fail closed to the generic Message", kind)
		require.JSONEq(t, `{}`, string(log.Details), "malformed kind %q must fail the payload closed to an empty object", kind)
	}
}

// TestEventResponseDiscriminatorFailClosesOnMalformedKindBytes proves the
// top-level Event DTO Type field fails closed on a malformed persisted kind and
// the payload projects to the scrubbed empty object.
func TestEventResponseDiscriminatorFailClosesOnMalformedKindBytes(t *testing.T) {
	now := time.Now().UTC()
	payload := `{"activationId":"a00000000000000000001","nodeId":"wnd_1","kind":"send_message","code":"message.delivery_failed","secret":"hunter2"}`
	for _, kind := range []string{
		" run_failed ",
		"run_failed\xff",
		"run_failed=SECRET",
		"future_unknown_kind",
		"",
	} {
		event := EventResponse(sampleEvent(kind, payload, now))
		require.Empty(t, event.Type, "malformed kind %q must fail closed to an empty Type", kind)
		require.JSONEq(t, `{}`, string(event.Payload), "malformed kind %q must fail the payload closed to an empty object", kind)
	}
}

// TestRunLogDiscriminatorSurvivesExactKindBytes proves an exact valid kind
// round-trips to Kind/Level/Message and the payload projects normally.
func TestRunLogDiscriminatorSurvivesExactKindBytes(t *testing.T) {
	now := time.Now().UTC()
	payload := `{"activationId":"a00000000000000000001","nodeId":"wnd_1","kind":"send_message","code":"message.delivery_failed"}`
	log := RunLogResponse(sampleEvent("run_failed", payload, now))
	require.Equal(t, "run_failed", log.Kind)
	require.Equal(t, "error", log.Level)
	require.Equal(t, "Run failed", log.Message)
	require.Contains(t, string(log.Details), `"activationId":"a00000000000000000001"`)
	require.Contains(t, string(log.Details), `"nodeId":"wnd_1"`)
	require.Contains(t, string(log.Details), `"code":"message.delivery_failed"`)
	require.NotContains(t, string(log.Details), "hunter2")
}

// TestTypedEventProjectorFailClosesOnScalarArrayNull proves the typed event
// projector returns nil (not the unchanged scalar/array/null) for a
// non-object top-level payload.
func TestTypedEventProjectorFailClosesOnScalarArrayNull(t *testing.T) {
	for _, payload := range []string{
		`"just a string"`,
		`[1,2,3]`,
		`null`,
		`42`,
		`true`,
	} {
		require.Nil(t, safeOperationalEventJSON(json.RawMessage(payload), "run_failed"), "scalar/array/null event payload must fail closed: %s", payload)
	}
}

// TestEffectResultProjectorFailClosesOnScalarArrayNull proves the typed
// EffectResult projector returns nil for a non-object top-level payload.
func TestEffectResultProjectorFailClosesOnScalarArrayNull(t *testing.T) {
	for _, payload := range []string{
		`"just a string"`,
		`[1,2,3]`,
		`null`,
		`42`,
		`true`,
	} {
		require.Nil(t, safeEffectResultJSON(json.RawMessage(payload)), "scalar/array/null EffectResult payload must fail closed: %s", payload)
	}
}

// TestOperationalProjectorFailClosesOnScalarArrayNull proves the operational
// scrubber (ErrorJSON/RequestJSON) returns nil for a non-object top-level
// payload so no unbounded scalar can reach the public DTO.
func TestOperationalProjectorFailClosesOnScalarArrayNull(t *testing.T) {
	for _, payload := range []string{
		`"just a string"`,
		`[1,2,3]`,
		`null`,
		`42`,
	} {
		require.Nil(t, safeOperationalJSON(json.RawMessage(payload)), "scalar/array/null operational payload must fail closed: %s", payload)
	}
}

// TestMalformedKindDoesNotUnlockPayloadProjection proves a padded kind's payload
// cannot be projected through the trimmed schema lookup: the byte-exact
// producer validator is authoritative and the payload stays an empty object even
// though eventKindSchema would trim the kind into a valid schema.
func TestMalformedKindDoesNotUnlockPayloadProjection(t *testing.T) {
	now := time.Now().UTC()
	payload := `{"activationId":"a00000000000000000001","nodeId":"wnd_1","kind":"send_message","code":"message.delivery_failed","secret":"hunter2"}`
	log := RunLogResponse(sampleEvent(" run_failed ", payload, now))
	require.JSONEq(t, `{}`, string(log.Details), "a padded kind must not unlock the payload projection through a trimmed schema")
}
