package combat_semantics

import (
	"context"
	"strings"
	"testing"
	"time"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
	"gitea.maximumdirect.net/eric/promptkit"
)

func TestRegisterPromptAssetsPreparesCombatSemanticsPrompt(t *testing.T) {
	registry := llm.NewAssetRegistry()
	if err := RegisterPromptAssets(registry); err != nil {
		t.Fatalf("RegisterPromptAssets() error = %v, want nil", err)
	}
	engine := newCombatSemanticsPromptEngine(t, registry)
	inputs := map[string]promptkit.ArtifactRef{
		"transcript": promptkit.InlineWithURI("file:///session.json", `{"sentinel":"combat-semantics-transcript"}`),
	}
	prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
		PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "combat-semantics-test-profile", Inputs: inputs,
	})
	if err != nil {
		t.Fatalf("Prepare() error = %v, want nil", err)
	}
	if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_scene_combat_semantics_llm.v1.json" {
		t.Fatalf("prepared prompt = %#v, want combat-semantics prompt identity and schema wiring", prepared)
	}
	if len(prepared.Messages) != 4 {
		t.Fatalf("prepared message count = %d, want 4", len(prepared.Messages))
	}
	for index, message := range prepared.Messages {
		wantEphemeral := index == 3
		gotEphemeral := message.CacheControl != nil && message.CacheControl.Type == promptkit.CacheControlEphemeral
		if gotEphemeral != wantEphemeral {
			t.Fatalf("message %d cache control = %#v, want ephemeral=%t", index, message.CacheControl, wantEphemeral)
		}
	}
	assertRenderedExactlyOnce(t, prepared.Messages, "substantive active combat materially organizes the", 1)
	assertRenderedExactlyOnce(t, prepared.Messages, "combat-semantics-transcript", 3)
	for name := range inputs {
		missing := make(map[string]promptkit.ArtifactRef, len(inputs)-1)
		for inputName, input := range inputs {
			if inputName != name {
				missing[inputName] = input
			}
		}
		if _, err := engine.Prepare(context.Background(), promptkit.RunRequest{
			PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "combat-semantics-test-profile", Inputs: missing,
		}); err == nil {
			t.Fatalf("Prepare() without required %q input = nil error", name)
		}
	}
}

func TestPromptAssetMetadataIsContentSafe(t *testing.T) {
	hash, err := promptAssetMetadata()
	if err != nil || !strings.HasPrefix(hash, "sha256:") {
		t.Fatalf("promptAssetMetadata() = %q, %v; want hash", hash, err)
	}
	if strings.Contains(hash, "non_combat") {
		t.Fatalf("prompt asset metadata leaked raw prompt content: %q", hash)
	}
}

func newCombatSemanticsPromptEngine(t *testing.T, registry *llm.AssetRegistry) *promptkit.Engine {
	t.Helper()
	options, err := registry.PromptKitOptions()
	if err != nil {
		t.Fatalf("PromptKitOptions() error = %v, want nil", err)
	}
	options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
		ID: "combat-semantics-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "combat-semantics-test-model",
	})))
	engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
	if err != nil {
		t.Fatalf("NewEngine() error = %v, want nil", err)
	}
	return engine
}

func assertRenderedExactlyOnce(t *testing.T, messages []promptkit.RenderedMessage, sentinel string, wantIndex int) {
	t.Helper()
	index := -1
	occurrences := 0
	for messageIndex, message := range messages {
		count := strings.Count(message.Content, sentinel)
		if count > 0 {
			index = messageIndex
			occurrences += count
		}
	}
	if occurrences != 1 || index != wantIndex {
		t.Fatalf("sentinel %q rendered %d times at message %d, want once at message %d", sentinel, occurrences, index, wantIndex)
	}
}
