package layoutbundleslotv1

import (
	"bytes"
	"strings"
	"testing"
)

func TestCanonicalBehaviorInitialAndSuccessor(t *testing.T) {
	initial := newSlotPackageFixture(t, 1, "Issue", nil)
	initialRaw := initial.pkg.ComponentBlobs[initial.slotIndex].CanonicalBehavior
	parsed, err := ParseCanonicalBehavior(initialRaw)
	if err != nil {
		t.Fatal(err)
	}
	if parsed.PreviousTargetComponentID != nil || parsed.LocalComponentKey != fixtureLocalKey {
		t.Fatalf("initial behavior = %#v", parsed)
	}
	reproduced, err := CanonicalBehaviorBytes(parsed)
	if err != nil || !bytes.Equal(reproduced, initialRaw) {
		t.Fatalf("reproduced initial = %s, %v", reproduced, err)
	}

	initialTarget := parsed.TargetComponentID
	successor := newSlotPackageFixture(t, 2, "IssueV2", &initialTarget)
	successorRaw := successor.pkg.ComponentBlobs[successor.slotIndex].CanonicalBehavior
	parsed, err = ParseCanonicalBehavior(successorRaw)
	if err != nil {
		t.Fatal(err)
	}
	if parsed.PreviousTargetComponentID == nil || *parsed.PreviousTargetComponentID != initialTarget {
		t.Fatalf("successor behavior = %#v", parsed)
	}
}

func TestParseCanonicalBehaviorRejectsOpenOrNonCanonicalInput(t *testing.T) {
	fixture := newSlotPackageFixture(t, 1, "Issue", nil)
	canonical := fixture.pkg.ComponentBlobs[fixture.slotIndex].CanonicalBehavior
	targetNeedle := []byte(`"target_component_id":"`)
	duplicate := bytes.Replace(canonical, []byte(`{"local_component_key":`), []byte(`{"schema_version":"nexuscrm.layout-bundle-slot/v1","local_component_key":`), 1)
	escaped := bytes.Replace(canonical, []byte("key256:"), []byte(`key\u003256:`), 1)
	reordered := bytes.Replace(canonical, targetNeedle, []byte(`"z":"x","target_component_id":"`), 1)
	missing := bytes.Replace(canonical, []byte(`,"schema_version":"nexuscrm.layout-bundle-slot/v1"`), nil, 1)
	nullValue := bytes.Replace(canonical, []byte(`"schema_version":"nexuscrm.layout-bundle-slot/v1"`), []byte(`"schema_version":null`), 1)
	nonString := bytes.Replace(canonical, []byte(`"schema_version":"nexuscrm.layout-bundle-slot/v1"`), []byte(`"schema_version":1`), 1)
	for name, raw := range map[string][]byte{
		"leading whitespace":  append([]byte(" "), canonical...),
		"trailing whitespace": append(append([]byte(nil), canonical...), '\n'),
		"trailing JSON":       append(append([]byte(nil), canonical...), []byte(` true`)...),
		"duplicate":           duplicate,
		"escaped ASCII":       escaped,
		"unknown":             reordered,
		"missing":             missing,
		"null":                nullValue,
		"non-string":          nonString,
		"array":               []byte(`[]`),
		"invalid UTF-8":       append(append([]byte(nil), canonical...), 0xff),
	} {
		t.Run(name, func(t *testing.T) {
			if _, err := ParseCanonicalBehavior(raw); err == nil {
				t.Fatalf("ParseCanonicalBehavior(%q) succeeded", raw)
			}
		})
	}
}

func TestCanonicalBehaviorRejectsInvalidScalarFacts(t *testing.T) {
	validTarget := "sha256:" + strings.Repeat("a", 64)
	base := Behavior{SchemaVersion: BehaviorSchemaVersion, LocalComponentKey: fixtureLocalKey, TargetComponentID: validTarget}
	mutations := map[string]func(*Behavior){
		"schema":      func(value *Behavior) { value.SchemaVersion = "v1" },
		"local key":   func(value *Behavior) { value.LocalComponentKey = "key256:bad" },
		"target":      func(value *Behavior) { value.TargetComponentID = "sha256:bad" },
		"predecessor": func(value *Behavior) { value.PreviousTargetComponentID = pointer("sha256:bad") },
		"same target": func(value *Behavior) { value.PreviousTargetComponentID = pointer(validTarget) },
	}
	for name, mutate := range mutations {
		t.Run(name, func(t *testing.T) {
			changed := base
			mutate(&changed)
			if _, err := CanonicalBehaviorBytes(changed); err == nil {
				t.Fatalf("CanonicalBehaviorBytes(%#v) succeeded", changed)
			}
		})
	}
}
