package module

import (
	"path/filepath"
	"regexp"
	"testing"
)

// TestAllManifestsLoadAndEventRegexesCompile is the repo-wide consistency
// gate for module manifests: every modules/<id>/module.yaml must LoadDir
// cleanly, and every declared events regex must compile under Go's RE2
// engine (AMPTemplates uses .NET (?<n>) syntax — these must have been
// converted to (?P<n>)) and carry at least one named capture group the
// agent's tracker actually reads (see agent/internal/state/tracker.go
// OnLogLine: name/player_name, id-family, msg/detail/reason).
func TestAllManifestsLoadAndEventRegexesCompile(t *testing.T) {
	dir := filepath.Join("..", "..", "modules")
	manifests, err := LoadDir(dir)
	if err != nil {
		t.Fatalf("LoadDir(%s): %v", dir, err)
	}
	if len(manifests) < 20 {
		t.Fatalf("expected >=20 manifests, got %d — wrong dir?", len(manifests))
	}

	// Group names the tracker extracts (tracker.go firstGroup calls).
	known := map[string]bool{
		"name": true, "player_name": true,
		"platform_id": true, "owner": true, "cross_id": true, "id": true, "player_id": true,
		"msg": true, "detail": true, "reason": true,
	}
	validKinds := map[string]bool{"join": true, "leave": true, "chat": true, "death": true, "custom": true}

	for _, m := range manifests {
		m := m
		t.Run(m.ID, func(t *testing.T) {
			for evName, ev := range m.Events {
				re, err := regexp.Compile(ev.Pattern)
				if err != nil {
					t.Errorf("event %q: pattern does not compile: %v", evName, err)
					continue
				}
				if !validKinds[ev.Kind] {
					t.Errorf("event %q: unknown kind %q", evName, ev.Kind)
				}
				// Every event must expose at least one group the tracker
				// knows how to read, otherwise the event emits empty
				// PlayerName/PlayerId/Detail and is a silently-useless knob.
				got := false
				for _, g := range re.SubexpNames() {
					if known[g] {
						got = true
					}
				}
				if !got {
					t.Errorf("event %q: no tracker-known named group (want one of name/id/msg families); groups=%v", evName, re.SubexpNames())
				}
				// join/leave should identify the player by name or id.
				if ev.Kind == "join" || ev.Kind == "leave" {
					hasIdent := false
					for _, g := range re.SubexpNames() {
						if g == "name" || g == "player_name" || g == "id" || g == "platform_id" || g == "player_id" || g == "owner" || g == "cross_id" {
							hasIdent = true
						}
					}
					if !hasIdent {
						t.Errorf("event %q (kind %s): no player-identifying named group", evName, ev.Kind)
					}
				}
			}
		})
	}
}
