package catalogapp

import (
	"strings"
	"testing"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/appidentitykeydeclarationv1"
)

func TestVerifyReleaseComponentSetProjectsIdentityKeyDeclarations(t *testing.T) {
	verified := testIdentityKeyDeclarationPackage(t, false)
	set, err := verifyReleaseComponentSet(verified.Package)
	if err != nil {
		t.Fatal(err)
	}
	if len(set.layoutBundleSlots) != 0 {
		t.Fatalf("layout slot declarations = %#v, want none", set.layoutBundleSlots)
	}
	if len(set.identityKeyFacts.Profiles) != 1 || len(set.identityKeyFacts.Roles) != 1 {
		t.Fatalf("identity declarations = %#v, want one profile and one role", set.identityKeyFacts)
	}
	profile := set.identityKeyFacts.Profiles[0]
	role := set.identityKeyFacts.Roles[0]
	if profile.AppID != verified.Manifest.AppID || profile.AppReleaseDigest != verified.AppReleaseDigest ||
		profile.Key.NamespaceID != verified.Manifest.AppID || profile.Key.LocalKey != "profile-a" ||
		role.AppID != verified.Manifest.AppID || role.AppReleaseDigest != verified.AppReleaseDigest ||
		role.Key.NamespaceID != verified.Manifest.AppID || role.Key.LocalKey != "role-a" {
		t.Fatalf("identity declaration tuple = profile %#v role %#v", profile, role)
	}
	copy := cloneIdentityKeyDeclarations(set.identityKeyFacts)
	copy.Profiles[0].Key.LocalKey = "mutated"
	if set.identityKeyFacts.Profiles[0].Key.LocalKey != "profile-a" {
		t.Fatal("identity declaration clone aliases its source slice")
	}
}

func TestVerifyReleaseComponentSetRejectsMalformedIdentityKeyDeclaration(t *testing.T) {
	verified := testIdentityKeyDeclarationPackage(t, true)
	if _, err := verifyReleaseComponentSet(verified.Package); err == nil || !strings.Contains(err.Error(), "Profile key declaration") {
		t.Fatalf("malformed identity declaration = %v", err)
	}
}

func TestSealedIdentityKeyDeclarationsBindsExactReleaseTuple(t *testing.T) {
	verified := testIdentityKeyDeclarationPackage(t, false)
	release := registeredRelease(verified)
	declarations, err := sealedIdentityKeyDeclarations(release.AppID, release.AppReleaseDigest, release)
	if err != nil {
		t.Fatal(err)
	}
	if len(declarations.Profiles) != 1 || len(declarations.Roles) != 1 {
		t.Fatalf("sealed identity declarations = %#v", declarations)
	}
	if _, err := sealedIdentityKeyDeclarations("io.example.other", release.AppReleaseDigest, release); err == nil {
		t.Fatal("sealed identity declarations accepted a different AppID")
	}
	wrongRelease := release
	wrongRelease.AppID = "io.example.other"
	if _, err := sealedIdentityKeyDeclarations(release.AppID, release.AppReleaseDigest, wrongRelease); err == nil {
		t.Fatal("sealed identity declarations accepted a release with a different AppID")
	}
	if _, err := sealedIdentityKeyDeclarations(release.AppID, testDigest("other-release"), release); err == nil {
		t.Fatal("sealed identity declarations accepted a different release digest")
	}
}

func testIdentityKeyDeclarationPackage(t *testing.T, malformedProfileBehavior bool) appauthority.VerifiedPackage {
	return testIdentityKeyDeclarationPackageForApp(
		t,
		"io.example.identity",
		"io.example",
		"1.0.0",
		malformedProfileBehavior,
	)
}

func testIdentityKeyDeclarationPackageForApp(
	t *testing.T,
	appID, publisherID, semanticVersion string,
	malformedProfileBehavior bool,
) appauthority.VerifiedPackage {
	t.Helper()
	profileBehavior, err := appidentitykeydeclarationv1.CanonicalBehaviorBytes(
		appidentitykeydeclarationv1.Behavior{SchemaVersion: appidentitykeydeclarationv1.BehaviorSchemaVersion},
	)
	if err != nil {
		t.Fatal(err)
	}
	if malformedProfileBehavior {
		profileBehavior = []byte(`{}`)
	}
	roleBehavior, err := appidentitykeydeclarationv1.CanonicalBehaviorBytes(
		appidentitykeydeclarationv1.Behavior{SchemaVersion: appidentitykeydeclarationv1.BehaviorSchemaVersion},
	)
	if err != nil {
		t.Fatal(err)
	}
	profile := testIdentityKeyDeclarationComponent(t, appID, appidentitykeydeclarationv1.ProfileComponentKind, "profile-a", profileBehavior)
	role := testIdentityKeyDeclarationComponent(t, appID, appidentitykeydeclarationv1.RoleComponentKind, "role-a", roleBehavior)
	components := []appauthority.Component{profile, role}
	if components[1].ComponentID < components[0].ComponentID {
		components[0], components[1] = components[1], components[0]
	}
	blobs := make([]appauthority.ComponentBlob, len(components))
	for index, component := range components {
		behavior := roleBehavior
		if component.ComponentKind == appidentitykeydeclarationv1.ProfileComponentKind {
			behavior = profileBehavior
		}
		blobs[index] = appauthority.ComponentBlob{ComponentID: component.ComponentID, CanonicalBehavior: behavior}
	}
	manifest := appauthority.AppManifest{
		SchemaVersion:              appauthority.AppManifestSchemaVersion,
		AppID:                      appID,
		PublisherID:                publisherID,
		SemanticVersion:            semanticVersion,
		MetadataBundleDigest:       "sha256:" + strings.Repeat("0", 64),
		Dependencies:               []appauthority.Dependency{},
		OwnedNamespaces:            []string{appID},
		RequiredProtocols:          []appauthority.ProtocolRequirement{},
		RequiredEngineCapabilities: []string{},
		ProvidedCapabilities:       []appauthority.ProvidedCapability{},
		FrontendCompatibility:      appauthority.FrontendCompatibility{Mode: appauthority.FrontendModeNone},
	}
	bundle := appauthority.MetadataBundle{SchemaVersion: appauthority.MetadataBundleSchemaVersion, Components: components}
	manifest.MetadataBundleDigest, err = appauthority.MetadataBundleDigest(bundle, manifest)
	if err != nil {
		t.Fatal(err)
	}
	verified, err := appauthority.VerifyPackage(appauthority.Package{Manifest: manifest, Bundle: bundle, ComponentBlobs: blobs})
	if err != nil {
		t.Fatal(err)
	}
	return verified
}

func testIdentityKeyDeclarationComponent(t *testing.T, namespaceID, kind, localKey string, behavior []byte) appauthority.Component {
	t.Helper()
	digest, err := appauthority.ComponentDigest(kind, appidentitykeydeclarationv1.ComponentFormatVersion, behavior)
	if err != nil {
		t.Fatal(err)
	}
	component := appauthority.Component{
		ComponentKind: kind, ComponentFormatVersion: appidentitykeydeclarationv1.ComponentFormatVersion,
		NamespaceID: namespaceID, BusinessKey: localKey,
		DefinitionVersion: appidentitykeydeclarationv1.DefinitionVersion,
		LocalDependencies: []string{}, ExternalComponentRefs: []appauthority.ExternalComponentRef{},
		OverridePolicy: appauthority.OverridePolicyReplace,
		RemovalPolicy:  appauthority.RemovalPolicyBlockIfReferencedOrOverridden,
		ContentDigest:  digest,
	}
	component.ComponentID = appauthority.ComponentID(component.NamespaceID, component.ComponentKind, component.BusinessKey)
	return component
}
