package semanticreconcile

import (
	"testing"

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

func TestEngineMetadataAndFingerprintsCoverCoreIdentity(t *testing.T) {
	base := newTestEngine(t, &recordingReconciliationClient{}, DefaultLimits())
	metadata := base.ManifestMetadata()
	for _, key := range []string{
		"prompt_id", "prompt_version", "prompt_sha256",
		"response_schema_key", "response_schema_id", "response_schema_name", "response_schema_version", "response_schema_sha256",
		"semantic_reconciliation_policy", "semantic_reconciliation_limits",
	} {
		if metadata[key] == nil || metadata[key] == "" {
			t.Fatalf("metadata[%q] = %#v, want populated core identity", key, metadata[key])
		}
	}
	limits, ok := metadata["semantic_reconciliation_limits"].(map[string]any)
	if !ok || len(limits) != 3 || limits["context_radius"] == nil || limits["maximum_candidates"] == nil || limits["maximum_material_bytes"] == nil {
		t.Fatalf("limit metadata = %#v, want complete limits", metadata["semantic_reconciliation_limits"])
	}
	fingerprints := base.CheckpointFingerprints()
	wantNames := []string{"prompt", "response_schema", "semantic_reconciliation_policy", "semantic_reconciliation_limits"}
	if len(fingerprints) != len(wantNames) {
		t.Fatalf("fingerprints = %#v, want required categories", fingerprints)
	}
	for index, want := range wantNames {
		if fingerprints[index].Name != want || fingerprints[index].Value == "" {
			t.Fatalf("fingerprint %d = %#v, want %q with value", index, fingerprints[index], want)
		}
	}

	metadata["prompt_id"] = "changed"
	limits["context_radius"] = -1
	fingerprints[0].Name = "changed"
	if got := base.ManifestMetadata(); got["prompt_id"] == "changed" || got["semantic_reconciliation_limits"].(map[string]any)["context_radius"] == -1 {
		t.Fatalf("ManifestMetadata() exposed retained state: %#v", got)
	}
	if got := base.CheckpointFingerprints(); got[0].Name == "changed" {
		t.Fatalf("CheckpointFingerprints() exposed retained state: %#v", got)
	}
}

func TestCoreFingerprintsChangeWithBehavioralIdentity(t *testing.T) {
	base := newTestEngine(t, &recordingReconciliationClient{}, DefaultLimits())
	baseFingerprints := base.CheckpointFingerprints()

	promptChanged, err := NewEngine(&recordingReconciliationClient{}, testPromptSpec("b"), DefaultLimits())
	if err != nil {
		t.Fatal(err)
	}
	assertOnlyFingerprintChanged(t, baseFingerprints, promptChanged.CheckpointFingerprints(), "prompt")

	schema, err := LoadResponseSchema()
	if err != nil {
		t.Fatal(err)
	}
	schema.SHA256 = testDigest("b")
	schemaChanged := newEngine(&recordingReconciliationClient{}, base.prompt, schema, DefaultLimits())
	assertOnlyFingerprintChanged(t, baseFingerprints, schemaChanged.CheckpointFingerprints(), "response_schema")

	limits := DefaultLimits()
	limits.ContextRadius++
	limitsChanged, err := NewEngine(&recordingReconciliationClient{}, base.prompt, limits)
	if err != nil {
		t.Fatal(err)
	}
	assertOnlyFingerprintChanged(t, baseFingerprints, limitsChanged.CheckpointFingerprints(), "semantic_reconciliation_limits")
}

func TestInvalidEngineIdentityHasNoMetadataOrFingerprints(t *testing.T) {
	invalid := newEngine(&recordingReconciliationClient{}, testPromptSpec("a"), llm.ResponseSchema{}, DefaultLimits())
	if invalid.ManifestMetadata() != nil || invalid.CheckpointFingerprints() != nil {
		t.Fatalf("invalid engine exposed identity: metadata %#v fingerprints %#v", invalid.ManifestMetadata(), invalid.CheckpointFingerprints())
	}
}

func assertOnlyFingerprintChanged(t *testing.T, before, after []pipeline.CheckpointFingerprint, changedName string) {
	t.Helper()
	if len(before) != len(after) {
		t.Fatalf("fingerprint counts differ: %#v %#v", before, after)
	}
	for index := range before {
		changed := before[index] != after[index]
		if changed != (before[index].Name == changedName) {
			t.Fatalf("fingerprint %q change = %t, want only %q changed\nbefore: %#v\nafter: %#v", before[index].Name, changed, changedName, before, after)
		}
	}
}
