package layoutauthoringv1

import (
	"encoding/json"
	"testing"
)

func TestCanonicalDigestStabilityAndTransportExclusions(t *testing.T) {
	request := validSubmit(validCreateOperation())
	digest, err := RequestDigest(request)
	if err != nil {
		t.Fatal(err)
	}
	raw, err := json.Marshal(request)
	if err != nil {
		t.Fatal(err)
	}
	var roundTrip SubmitRequest
	if err := json.Unmarshal(raw, &roundTrip); err != nil {
		t.Fatal(err)
	}
	roundTripDigest, err := RequestDigest(roundTrip)
	if err != nil {
		t.Fatal(err)
	}
	if roundTripDigest != digest {
		t.Fatalf("round-trip digest = %q, want %q", roundTripDigest, digest)
	}

	for name, mutate := range map[string]func(*SubmitRequest){
		"wait mode":       func(value *SubmitRequest) { value.WaitMode = ReturnAfterAcceptance },
		"command id":      func(value *SubmitRequest) { value.CommandID = "another-command" },
		"idempotency key": func(value *SubmitRequest) { value.IdempotencyKey = "another-key" },
	} {
		t.Run(name, func(t *testing.T) {
			changed := request
			mutate(&changed)
			got, err := RequestDigest(changed)
			if err != nil {
				t.Fatal(err)
			}
			if got != digest {
				t.Fatalf("transport/acceptance-only field changed digest: got %q want %q", got, digest)
			}
		})
	}
}

func TestCanonicalDigestSensitiveToIdentityBearingFields(t *testing.T) {
	base := validSubmit(validCreateOperation())
	baseDigest, err := RequestDigest(base)
	if err != nil {
		t.Fatal(err)
	}
	mutations := map[string]func(*SubmitRequest){
		"base catalog revision": func(value *SubmitRequest) { value.BaseCatalogRevision++ },
		"document layout id":    func(value *SubmitRequest) { value.Operation.Create.Document.LayoutID += "-other" },
		"document object id":    func(value *SubmitRequest) { value.Operation.Create.Document.ObjectID = "Contact" },
		"document name":         func(value *SubmitRequest) { value.Operation.Create.Document.Name += " changed" },
		"document kind":         func(value *SubmitRequest) { value.Operation.Create.Document.Kind = "Edit" },
		"legacy version":        func(value *SubmitRequest) { value.Operation.Create.Document.LayoutVersion++ },
		"compact order": func(value *SubmitRequest) {
			fields := value.Operation.Create.Document.CompactFields
			fields[0], fields[1] = fields[1], fields[0]
		},
		"tabs":    func(value *SubmitRequest) { value.Operation.Create.Document.Tabs[0] = "summary" },
		"section": func(value *SubmitRequest) { value.Operation.Create.Document.Sections[0].Label += " changed" },
		"section kind presence": func(value *SubmitRequest) {
			value.Operation.Create.Document.Sections[0].Kind = nil
		},
		"field placement": func(value *SubmitRequest) {
			value.Operation.Create.Document.Sections[0].Fields[1] = "OwnerId.Email"
		},
		"orphan field label": func(value *SubmitRequest) {
			value.Operation.Create.Document.Sections[0].FieldLabels[1].Label += " changed"
		},
		"field behavior": func(value *SubmitRequest) {
			(*value.Operation.Create.Document.Sections[0].FieldBehaviors[0].Behaviors)[0].Behavior = "shown"
		},
		"related list": func(value *SubmitRequest) { value.Operation.Create.Document.RelatedLists[0].Label += " changed" },
		"prefill mapping": func(value *SubmitRequest) {
			value.Operation.Create.Document.RelatedLists[0].PrefillMappings[0].SourceFieldID = "OwnerId"
		},
		"panel":         func(value *SubmitRequest) { value.Operation.Create.Document.RecordPanels[0].Order++ },
		"header action": func(value *SubmitRequest) { value.Operation.Create.Document.HeaderActions.Actions[0].Kind = "Custom" },
		"extension value": func(value *SubmitRequest) {
			value.Operation.Create.Document.HeaderActions.Actions[0].Extensions[0].Value = stringValue("different")
		},
		"quick authored":    func(value *SubmitRequest) { value.Operation.Create.Document.QuickActions.Authored = false },
		"record state":      func(value *SubmitRequest) { value.Operation.Create.Document.RecordStates[0].Priority++ },
		"primary actions":   func(value *SubmitRequest) { value.Operation.Create.Document.PrimaryHeaderActions[0] = "host:clone" },
		"list fields":       func(value *SubmitRequest) { (*value.Operation.Create.ListFields.Value)[0] = "Email" },
		"workspace version": func(value *SubmitRequest) { *value.Operation.Create.ExpectedWorkspaceVersion += "-other" },
	}
	for name, mutate := range mutations {
		t.Run(name, func(t *testing.T) {
			raw, err := json.Marshal(base)
			if err != nil {
				t.Fatal(err)
			}
			var changed SubmitRequest
			if err := json.Unmarshal(raw, &changed); err != nil {
				t.Fatal(err)
			}
			mutate(&changed)
			digest, err := RequestDigest(changed)
			if err != nil {
				t.Fatal(err)
			}
			if digest == baseDigest {
				t.Fatal("identity-bearing change did not change digest")
			}
		})
	}
}

func TestCanonicalRequestVector(t *testing.T) {
	request := validSubmit(validCreateOperation())
	want := "sha256:8041092e471a4a76133b09d89c9a817097ba002a1040c69433e35f5fcda74310"
	got, err := RequestDigest(request)
	if err != nil {
		t.Fatal(err)
	}
	if got != want {
		t.Fatalf("canonical digest = %q, want %q", got, want)
	}
}

func TestEveryOperationArmHasDistinctCanonicalIdentity(t *testing.T) {
	name := "Renamed"
	changes := preserveChanges()
	changes.Name = Change[string]{Mode: ChangeSet, Value: &name}
	operations := []CommandOperation{
		validCreateOperation(),
		{Update: &UpdateLayout{LayoutID: "layout-main", ExpectedAggregateVersion: "aggregate-v1", Changes: changes, ListFields: FieldIDListChange{Mode: ChangePreserve}}},
		{Clone: &CloneLayout{SourceLayoutID: "layout-main", SourceResourceVersion: "resource-v1", TargetLayoutID: "layout-copy", TargetName: "Copy", ExpectedWorkspaceVersion: "workspace-v1"}},
		{SetDefault: &SetDefaultLayout{ObjectID: "Account", LayoutID: "layout-main", ExpectedWorkspaceVersion: "workspace-v1"}},
		{Delete: &DeleteLayout{LayoutID: "layout-main", ExpectedResourceVersion: "resource-v1", ExpectedWorkspaceVersion: "workspace-v1"}},
		{Restore: &RestoreLayout{LayoutID: "layout-main", HistoryVersion: 1, ExpectedResourceVersion: "resource-v1"}},
		{AssignmentUpsert: &UpsertLayoutAssignment{ObjectID: "Account", LayoutID: "layout-main", Selectors: AssignmentSelectors{RoleID: pointer("role-a")}, ExpectedWorkspaceVersion: "workspace-v1"}},
		{AssignmentDelete: &DeleteLayoutAssignment{AssignmentID: "assignment-a", ExpectedResourceVersion: "resource-v1", ExpectedWorkspaceVersion: "workspace-v1"}},
	}
	seen := map[string]struct{}{}
	for _, operation := range operations {
		digest, err := RequestDigest(validSubmit(operation))
		if err != nil {
			t.Fatal(err)
		}
		if _, duplicate := seen[digest]; duplicate {
			t.Fatalf("operation digest collision: %s", digest)
		}
		seen[digest] = struct{}{}
	}
}
