package outboundintentv1

import (
	"strings"
	"testing"
)

func TestLogicalEffectIdentityHasFrozenGrammar(t *testing.T) {
	scope := logicalEffectTestScope()
	intentID, idempotencyKey, err := LogicalEffectIdentity(
		scope, "effect-root-1", "effect-slot-1", "generation-1",
	)
	if err != nil {
		t.Fatal(err)
	}
	const wantIntentID = "runtime-outbound-intent-v1:2c8f247a44d87bba3f09c5c46b186a3060ab99c369026807ae22cba5f7ca8be0"
	const wantIdempotencyKey = "runtime-outbound-effect-v1:4a3dc414d1f6885ee4da2af2caaf3ecc2d80354ad2c75bc098aabaae9bf797db"
	if intentID != wantIntentID || idempotencyKey != wantIdempotencyKey {
		t.Fatalf("identity = (%q, %q), want (%q, %q)", intentID, idempotencyKey, wantIntentID, wantIdempotencyKey)
	}

	variants := []struct {
		name       string
		scope      LogicalEffectScope
		root, slot string
		generation string
	}{
		{name: "org", scope: LogicalEffectScope{OrgID: "org-2", AppInstallationID: scope.AppInstallationID, AuthorizedWorkloadID: scope.AuthorizedWorkloadID}, root: "effect-root-1", slot: "effect-slot-1", generation: "generation-1"},
		{name: "installation", scope: LogicalEffectScope{OrgID: scope.OrgID, AppInstallationID: "installation-2", AuthorizedWorkloadID: scope.AuthorizedWorkloadID}, root: "effect-root-1", slot: "effect-slot-1", generation: "generation-1"},
		{name: "workload", scope: LogicalEffectScope{OrgID: scope.OrgID, AppInstallationID: scope.AppInstallationID, AuthorizedWorkloadID: "integration-2"}, root: "effect-root-1", slot: "effect-slot-1", generation: "generation-1"},
		{name: "root", scope: scope, root: "effect-root-2", slot: "effect-slot-1", generation: "generation-1"},
		{name: "slot", scope: scope, root: "effect-root-1", slot: "effect-slot-2", generation: "generation-1"},
		{name: "generation", scope: scope, root: "effect-root-1", slot: "effect-slot-1", generation: "generation-2"},
	}
	for _, variant := range variants {
		t.Run(variant.name, func(t *testing.T) {
			otherIntentID, otherIdempotencyKey, otherErr := LogicalEffectIdentity(
				variant.scope, variant.root, variant.slot, variant.generation,
			)
			if otherErr != nil {
				t.Fatal(otherErr)
			}
			if otherIntentID == intentID || otherIdempotencyKey == idempotencyKey {
				t.Fatalf("variant aliased frozen identity: (%q, %q)", otherIntentID, otherIdempotencyKey)
			}
		})
	}
}

func TestLogicalEffectIdentityLengthPrefixesPreventTupleAliasing(t *testing.T) {
	scope := logicalEffectTestScope()
	leftIntentID, leftKey, leftErr := LogicalEffectIdentity(scope, "a", "bc", "generation")
	rightIntentID, rightKey, rightErr := LogicalEffectIdentity(scope, "ab", "c", "generation")
	if leftErr != nil || rightErr != nil {
		t.Fatalf("identity errors = (%v, %v)", leftErr, rightErr)
	}
	if leftIntentID == rightIntentID || leftKey == rightKey {
		t.Fatal("adjacent variable-length tuple parts aliased")
	}
}

func TestLogicalEffectIdentityUsesByteBounds(t *testing.T) {
	maxIdentifier := strings.Repeat("x", MaxIdentifierBytes)
	tooLongIdentifier := maxIdentifier + "x"
	maximumMultibyteIdentifier := strings.Repeat("界", MaxIdentifierBytes/len("界"))
	if len(maximumMultibyteIdentifier) != MaxIdentifierBytes {
		t.Fatalf("multibyte fixture length = %d", len(maximumMultibyteIdentifier))
	}

	tests := []struct {
		name   string
		mutate func(*LogicalEffectScope, *string, *string, *string)
	}{
		{name: "org", mutate: func(scope *LogicalEffectScope, _, _, _ *string) { scope.OrgID = maxIdentifier }},
		{name: "installation", mutate: func(scope *LogicalEffectScope, _, _, _ *string) { scope.AppInstallationID = maxIdentifier }},
		{name: "root multibyte", mutate: func(_ *LogicalEffectScope, root, _, _ *string) { *root = maximumMultibyteIdentifier }},
		{name: "slot", mutate: func(_ *LogicalEffectScope, _, slot, _ *string) { *slot = maxIdentifier }},
		{name: "generation", mutate: func(_ *LogicalEffectScope, _, _, generation *string) { *generation = maxIdentifier }},
	}
	for _, test := range tests {
		t.Run(test.name+" maximum", func(t *testing.T) {
			scope := logicalEffectTestScope()
			root, slot, generation := "mail-request-1", "slot", "generation"
			test.mutate(&scope, &root, &slot, &generation)
			if _, _, err := LogicalEffectIdentity(scope, root, slot, generation); err != nil {
				t.Fatalf("maximum rejected: %v", err)
			}
		})
		t.Run(test.name+" over maximum", func(t *testing.T) {
			scope := logicalEffectTestScope()
			root, slot, generation := "mail-request-1", "slot", "generation"
			test.mutate(&scope, &root, &slot, &generation)
			switch test.name {
			case "org":
				scope.OrgID = tooLongIdentifier
			case "installation":
				scope.AppInstallationID = tooLongIdentifier
			case "root multibyte":
				root = maximumMultibyteIdentifier + "x"
			case "slot":
				slot = tooLongIdentifier
			case "generation":
				generation = tooLongIdentifier
			}
			if _, _, err := LogicalEffectIdentity(scope, root, slot, generation); err == nil {
				t.Fatal("over-maximum identifier accepted")
			}
		})
	}
}

func TestLogicalEffectIdentityBoundsAndRequiredCoordinates(t *testing.T) {
	validScope := logicalEffectTestScope()
	validScope.AuthorizedWorkloadID = strings.Repeat("w", MaxAuthorizedWorkloadIDBytes)
	if _, _, err := LogicalEffectIdentity(validScope, "root", "slot", "generation"); err != nil {
		t.Fatalf("maximum workload ID rejected: %v", err)
	}

	for name, test := range map[string]struct {
		scope           LogicalEffectScope
		root, slot, gen string
	}{
		"missing org":          {scope: LogicalEffectScope{AppInstallationID: validScope.AppInstallationID, AuthorizedWorkloadID: validScope.AuthorizedWorkloadID}, root: "root", slot: "slot", gen: "generation"},
		"oversized org":        {scope: LogicalEffectScope{OrgID: strings.Repeat("o", MaxIdentifierBytes+1), AppInstallationID: validScope.AppInstallationID, AuthorizedWorkloadID: validScope.AuthorizedWorkloadID}, root: "root", slot: "slot", gen: "generation"},
		"missing install":      {scope: LogicalEffectScope{OrgID: validScope.OrgID, AuthorizedWorkloadID: validScope.AuthorizedWorkloadID}, root: "root", slot: "slot", gen: "generation"},
		"oversized workload":   {scope: LogicalEffectScope{OrgID: validScope.OrgID, AppInstallationID: validScope.AppInstallationID, AuthorizedWorkloadID: strings.Repeat("w", MaxAuthorizedWorkloadIDBytes+1)}, root: "root", slot: "slot", gen: "generation"},
		"missing root":         {scope: validScope, slot: "slot", gen: "generation"},
		"missing slot":         {scope: validScope, root: "root", gen: "generation"},
		"missing generation":   {scope: validScope, root: "root", slot: "slot"},
		"trimmed generation":   {scope: validScope, root: "root", slot: "slot", gen: " generation"},
		"control-bearing root": {scope: validScope, root: "root\x1f1", slot: "slot", gen: "generation"},
		"invalid UTF-8 root":   {scope: validScope, root: string([]byte{0xff}), slot: "slot", gen: "generation"},
	} {
		t.Run(name, func(t *testing.T) {
			if intentID, key, err := LogicalEffectIdentity(test.scope, test.root, test.slot, test.gen); err == nil || intentID != "" || key != "" {
				t.Fatalf("identity = (%q, %q, %v), want typed rejection", intentID, key, err)
			}
		})
	}
}

func logicalEffectTestScope() LogicalEffectScope {
	return LogicalEffectScope{
		OrgID: "org-1", AppInstallationID: "installation-1", AuthorizedWorkloadID: "integration-1",
	}
}
