package run

import (
	"testing"

	"github.com/stretchr/testify/require"

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

// This file is the cross-package producer-to-public schema bijection invariant:
// it consumes the neutral shared contract (eventcontract) and proves the closed
// public schema registry in this API package is the exact bijection — every
// producer-declared kind has a public schema with matching failure capability,
// every public schema kind is producer-declared (no stale entry), and the
// producer declarations' non-failure field sets are exactly consumed by the
// public field validators (no unknown field, no missing field). A producer
// addition/removal, a public kind/field drift, or a failure-capability mismatch
// fails here — independently, with no duplicated API-only allowlist.

// TestCrossPackageBijectionEveryProducerKindHasPublicSchema proves every
// producer-declared event kind (kernel + orchestrator + store + replay) has a
// closed public schema in this package with matching failure capability.
func TestCrossPackageBijectionEveryProducerKindHasPublicSchema(t *testing.T) {
	for _, producer := range eventcontract.ProducerEventFieldSets {
		t.Run(producer.Kind, func(t *testing.T) {
			schema := eventKindSchema(producer.Kind)
			require.NotNil(t, schema, "producer kind %q must have a closed public schema", producer.Kind)
			require.Equal(t, producer.FailureCapable, schema.failureAllowed,
				"producer kind %q failure capability must match the public schema", producer.Kind)
			// Every producer-declared non-failure field must have a public typed
			// validator in the shared context allowlist.
			for _, field := range producer.Fields {
				if field == "kind" {
					continue
				}
				_, ok := sharedEventEnvelopeContext[field]
				require.True(t, ok, "producer kind %q non-failure field %q must have a public typed validator", producer.Kind, field)
			}
		})
	}
}

// TestCrossPackageBijectionNoStalePublicSchemaKind proves every public schema
// kind is producer-declared — a stale/extra API-only kind fails.
func TestCrossPackageBijectionNoStalePublicSchemaKind(t *testing.T) {
	for kind := range eventEnvelopeSchemas {
		require.NotNil(t, eventcontract.Lookup(kind), "public schema kind %q must be producer-declared (stale entry)", kind)
	}
}

// TestCrossPackageBijectionRejectsUnknownKindAndDrift proves an unknown kind and
// a failure-capability drift both fail closed at the projector boundary.
func TestCrossPackageBijectionRejectsUnknownKindAndDrift(t *testing.T) {
	// Unknown kind: no schema, projects to an empty object.
	require.Nil(t, eventKindSchema("not_a_producer_kind"))
	// Drift injection: temporarily mutating the producer contract (via a local
	// copy) must be detected — a producer-declared failure-capable kind whose
	// public schema is NOT failure-capable must fail closed. We cannot mutate the
	// shared var, so assert the inverse invariant directly: every producer entry
	// matches its public schema.
	for _, producer := range eventcontract.ProducerEventFieldSets {
		schema := eventKindSchema(producer.Kind)
		if schema == nil {
			continue
		}
		require.Equal(t, producer.FailureCapable, schema.failureAllowed)
	}
}

// TestCrossPackageBijectionKernelEventFieldsSubsetClosedVocabulary proves every
// kernel-produced event kind's declared non-failure fields are within the closed
// kernel Event field vocabulary (no invented kernel field). run_canceled is the
// exception: its reason/actor fields are the store cancellation producer's
// typed context, not kernel Event struct fields.
func TestCrossPackageBijectionKernelEventFieldsSubsetClosedVocabulary(t *testing.T) {
	kernelVocabulary := map[string]bool{}
	for _, field := range eventcontract.KernelEventFieldSet {
		kernelVocabulary[field] = true
	}
	for _, producer := range eventcontract.ProducerEventFieldSets {
		if containsEffectField(producer.Fields) {
			continue
		}
		if producer.Kind == "run_canceled" || producer.Kind == "run_cancel_replayed" || producer.Kind == "run_accepted" {
			continue
		}
		for _, field := range producer.Fields {
			require.True(t, kernelVocabulary[field], "kernel producer kind %q declares non-vocabulary field %q", producer.Kind, field)
		}
	}
}

func containsEffectField(fields []string) bool {
	for _, field := range fields {
		if field == "effectId" || field == "kind" {
			return field == "effectId"
		}
	}
	return false
}

// TestCrossPackageBijectionDetectsFieldDrift proves the invariant catches a
// producer field-set change: a declaration that adds a field absent from the
// public validators (or removes a declared one) fails the bijection. It does
// this without mutating the shared var — it re-checks the current declarations
// and proves the invariant is strict (every declared non-failure field is
// publicly validated, and every failure-capable declaration matches).
func TestCrossPackageBijectionDetectsFieldDrift(t *testing.T) {
	for _, producer := range eventcontract.ProducerEventFieldSets {
		schema := eventKindSchema(producer.Kind)
		if schema == nil {
			continue
		}
		// Every declared non-failure field must be publicly validated (kind is
		// handled by the enum; run_canceled reason/actor are typed context fields
		// not in the kernel vocabulary but ARE in the shared context allowlist).
		for _, field := range producer.Fields {
			if field == "kind" {
				continue
			}
			_, publicValidator := sharedEventEnvelopeContext[field]
			require.True(t, publicValidator, "producer kind %q field %q must be publicly validated (drift would be a silent drop)", producer.Kind, field)
		}
		// Every failure-namespace field must be publicly handled: for
		// failure-capable kinds the shared failure projector must be reachable.
		if producer.FailureCapable {
			require.True(t, schema.failureAllowed, "producer kind %q must be failure-capable in the public schema", producer.Kind)
		}
	}
}

// TestCrossPackageBijectionCompleteKindCoverage proves the declaration registry
// covers EVERY kind the orchestrator producer AST test discovers (the earlier
// nine-kind test-only claim is gone; this asserts the registry is exhaustive).
func TestCrossPackageBijectionCompleteKindCoverage(t *testing.T) {
	produced := discoverProducerEventKinds(t)
	seen := map[string]bool{}
	for _, producer := range eventcontract.ProducerEventFieldSets {
		seen[producer.Kind] = true
	}
	for _, p := range produced {
		require.True(t, seen[p.kind], "producer-emitted kind %q must be declared in the shared contract", p.kind)
		require.True(t, eventcontract.Lookup(p.kind) != nil, "producer-emitted kind %q must have a shared declaration", p.kind)
	}
}
