package run

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

	"github.com/stretchr/testify/require"

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

// These tests consume the EXACT event shapes the real kernel / dispatcher /
// approval producers persist (derived from the producer source, not hand-written
// invented fields) through the public RunLogResponse / EventResponse / HTTP DTO
// projection surfaces. They prove the corrected per-kind schema carries the
// reviewed context exactly and drops non-producer fields.

// producerShapeEvent builds a durable store.Event whose PayloadJSON uses the
// exact field set the named producer emits.
func producerShapeEvent(t *testing.T, kind, payload string) store.Event {
	t.Helper()
	require.True(t, json.Valid([]byte(payload)), "payload must be valid JSON for kind %s", kind)
	return store.Event{
		ID: "wre_1", RunID: "wri_1", Seq: 1, Type: kind,
		PayloadJSON: json.RawMessage(payload), CreatedAt: time.Now().UTC(),
	}
}

// TestProducerShapeParallelOpenedCarriesActivationID proves the real
// parallel_opened producer shape {activationId, scopeId} projects exactly: the
// activationId is preserved (not dropped) by the corrected schema.
func TestProducerShapeParallelOpenedCarriesActivationID(t *testing.T) {
	event := producerShapeEvent(t, "parallel_opened", `{
		"kind":"parallel_opened","activationId":"a00000000000000000007","scopeId":"wsc_2",
		"leaseToken":"lease_secret"
	}`)
	for _, project := range []func(store.Event) []byte{
		func(e store.Event) []byte { return RunLogResponse(e).Details },
		func(e store.Event) []byte { return EventResponse(e).Payload },
	} {
		text := string(project(event))
		require.Contains(t, text, `"activationId":"a00000000000000000007"`, "parallel_opened must preserve the producer activationId")
		require.Contains(t, text, `"scopeId":"wsc_2"`)
		require.NotContains(t, text, "leaseToken")
		require.NotContains(t, text, "lease_secret")
	}
}

// TestProducerShapeFanOutOpenedCarriesNodeID proves the real fan_out_opened
// producer shape {activationId, nodeId, scopeId} projects exactly: the nodeId is
// preserved by the corrected schema.
func TestProducerShapeFanOutOpenedCarriesNodeID(t *testing.T) {
	event := producerShapeEvent(t, "fan_out_opened", `{
		"kind":"fan_out_opened","activationId":"a00000000000000000008","nodeId":"wnd_fan","scopeId":"wsc_3",
		"clientMessageId":"idem_secret"
	}`)
	for _, project := range []func(store.Event) []byte{
		func(e store.Event) []byte { return RunLogResponse(e).Details },
		func(e store.Event) []byte { return EventResponse(e).Payload },
	} {
		text := string(project(event))
		require.Contains(t, text, `"nodeId":"wnd_fan"`, "fan_out_opened must preserve the producer nodeId")
		require.Contains(t, text, `"activationId":"a00000000000000000008"`)
		require.Contains(t, text, `"scopeId":"wsc_3"`)
		require.NotContains(t, text, "clientMessageId")
		require.NotContains(t, text, "idem_secret")
	}
}

// TestProducerShapeFanOutResolvedCarriesPort proves the real fan_out_resolved
// producer shape {scopeId, port} projects exactly: the port survives.
func TestProducerShapeFanOutResolvedCarriesPort(t *testing.T) {
	event := producerShapeEvent(t, "fan_out_resolved", `{
		"kind":"fan_out_resolved","scopeId":"wsc_4","port":"completed",
		"leaseToken":"lease_secret"
	}`)
	for _, project := range []func(store.Event) []byte{
		func(e store.Event) []byte { return RunLogResponse(e).Details },
		func(e store.Event) []byte { return EventResponse(e).Payload },
	} {
		text := string(project(event))
		require.Contains(t, text, `"port":"completed"`, "fan_out_resolved must preserve the producer port")
		require.Contains(t, text, `"scopeId":"wsc_4"`)
		require.NotContains(t, text, "leaseToken")
	}
}

// TestProducerShapeApprovalDecidedDropsNonProducerFields proves the real
// approval_decided producer shape {effectId, activationId, nodeId,
// notificationId, decision, decidedByMemberId} projects exactly and the invented
// notificationRevision/completedAt fields (never emitted by the producer) are
// dropped.
func TestProducerShapeApprovalDecidedDropsNonProducerFields(t *testing.T) {
	event := producerShapeEvent(t, "approval_decided", `{
		"effectId":"wfx_da6422670e96e26689cb18e694d283ae","activationId":"a00000000000000000003","nodeId":"wnd_approve",
		"notificationId":"btn_baa86a9e8b16589a778ee2cfce0bef62","decision":"approved","decidedByMemberId":"000000000002",
		"notificationRevision":5,"completedAt":"2026-08-17T00:00:00Z","leaseToken":"lease_secret"
	}`)
	for _, project := range []func(store.Event) []byte{
		func(e store.Event) []byte { return RunLogResponse(e).Details },
		func(e store.Event) []byte { return EventResponse(e).Payload },
	} {
		text := string(project(event))
		for _, want := range []string{
			`"effectId":"wfx_da6422670e96e26689cb18e694d283ae"`, `"activationId":"a00000000000000000003"`,
			`"nodeId":"wnd_approve"`, `"notificationId":"btn_baa86a9e8b16589a778ee2cfce0bef62"`,
			`"decision":"approved"`, `"decidedByMemberId":"000000000002"`,
		} {
			require.Contains(t, text, want, "approval_decided must preserve producer field")
		}
		require.NotContains(t, text, "notificationRevision", "approval_decided must not invent notificationRevision")
		require.NotContains(t, text, "completedAt", "approval_decided must not invent completedAt")
		require.NotContains(t, text, "leaseToken")
	}
}

// TestProducerShapeLoopBrokenIsNotFailureCapable proves the real loop_broken
// producer shape {activationId, scopeId} is a plain event: injecting a failure
// contract on the non-failure-capable kind fails the whole envelope closed
// (never projects a partial event), so a stale failure marker cannot leak.
func TestProducerShapeLoopBrokenIsNotFailureCapable(t *testing.T) {
	event := producerShapeEvent(t, "loop_broken", `{
		"kind":"loop_broken","activationId":"a00000000000000000009","scopeId":"wsc_5",
		"code":"loop.broken_failure","problem":"should be dropped","hint":"should be dropped"
	}`)
	for _, project := range []func(store.Event) []byte{
		func(e store.Event) []byte { return RunLogResponse(e).Details },
		func(e store.Event) []byte { return EventResponse(e).Payload },
	} {
		text := string(project(event))
		require.NotContains(t, text, "loop.broken_failure", "loop_broken must not carry a failure contract")
		require.NotContains(t, text, "should be dropped")
		require.NotContains(t, text, `"activationId"`, "a failure injected on a non-failure kind fails the whole envelope closed")
	}
}

// TestProducerShapeLoopBrokenPlainEventProjectsContext proves a REAL loop_broken
// producer shape (no failure) projects its context exactly.
func TestProducerShapeLoopBrokenPlainEventProjectsContext(t *testing.T) {
	event := producerShapeEvent(t, "loop_broken", `{
		"kind":"loop_broken","activationId":"a00000000000000000009","scopeId":"wsc_5",
		"leaseToken":"lease_secret"
	}`)
	for _, project := range []func(store.Event) []byte{
		func(e store.Event) []byte { return RunLogResponse(e).Details },
		func(e store.Event) []byte { return EventResponse(e).Payload },
	} {
		text := string(project(event))
		require.Contains(t, text, `"activationId":"a00000000000000000009"`)
		require.Contains(t, text, `"scopeId":"wsc_5"`)
		require.NotContains(t, text, "leaseToken")
	}
}

// TestProducerShapeEffectResultDropsInventedFields proves the flat EffectResult
// consumed through EffectResponse drops the invented kind/status/retryAt/
// receiptRecovery fields (not orchestrator.EffectResult fields) while preserving
// the real producer context.
func TestProducerShapeEffectResultDropsInventedFields(t *testing.T) {
	now := time.Now().UTC()
	effect := store.Effect{
		ID: "wfx_baa86a9e8b16589a778ee2cfce0bef62", RunID: "wri_1", ActivationID: "wra_6b86b273ff34fce19d6b804eff5a3f57", Kind: store.EffectKindMessage,
		Status: store.EffectStatusFailed,
		ResultJSON: json.RawMessage(`{
			"port":"failed",
			"kind":"send_message","status":"awaiting","retryAt":"2026-08-17T00:00:00Z","receiptRecovery":true,
			"messageId":"msg_1","notificationId":"btn_baa86a9e8b16589a778ee2cfce0bef62",
			"callbackResponderAgentId":"agt_0000000000000a",
			"deliveryScope":{"kind":"task","scopeId":"tsk_1"},
			"leaseToken":"lease_secret"
		}`),
		CreatedAt: now, UpdatedAt: now,
	}
	text, err := json.Marshal(EffectResponse(effect))
	require.NoError(t, err)
	raw := string(text)
	require.Contains(t, raw, `"port":"failed"`)
	require.Contains(t, raw, `"messageId":"msg_1"`)
	require.Contains(t, raw, `"notificationId":"btn_baa86a9e8b16589a778ee2cfce0bef62"`)
	require.Contains(t, raw, `"callbackResponderAgentId":"agt_0000000000000a"`)
	require.Contains(t, raw, `"deliveryScope"`)
	require.NotContains(t, raw, `"retryAt":"2026-08-17T00:00:00Z"`, "EffectResult must not invent retryAt")
	require.NotContains(t, raw, `"receiptRecovery":true`, "EffectResult must not invent receiptRecovery")
	require.NotContains(t, raw, `"status":"awaiting"`, "EffectResult must not invent a flat status")
	require.NotContains(t, raw, "leaseToken")
}

// TestProducerShapePlanPortGrammarRejectsOversizePort proves the authoritative
// Plan port grammar rejects an oversize/punctuation-prefixed port while keeping
// a legitimate port.
func TestProducerShapePlanPortGrammarRejectsOversizePort(t *testing.T) {
	oversize := ""
	for index := 0; index < 70; index++ {
		oversize += "a"
	}
	event := producerShapeEvent(t, "fan_out_resolved", `{
		"kind":"fan_out_resolved","scopeId":"wsc_6","port":"`+oversize+`"
	}`)
	text := string(RunLogResponse(event).Details)
	require.NotContains(t, text, oversize, "oversize port must fail closed")
	require.NotContains(t, text, `"port"`, "oversize port must be dropped")
}
