package appidentitykeydeclarationv1

import (
	"bytes"
	"testing"

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

func TestVerifyPackageDeclarationsExtractsTypedPositiveFacts(t *testing.T) {
	fixture := newPackageFixture(t,
		fixtureDeclarationSpec{componentKind: ProfileComponentKind, namespaceID: fixtureNamespace, localKey: "billing.operator"},
		fixtureDeclarationSpec{componentKind: RoleComponentKind, namespaceID: fixtureNamespace, localKey: "billing.operator"},
	)
	verified, declarations, err := VerifyPackageDeclarations(fixture.pkg)
	if err != nil {
		t.Fatal(err)
	}
	if len(declarations.Profiles) != 1 || len(declarations.Roles) != 1 {
		t.Fatalf("declarations = %#v", declarations)
	}
	profile := declarations.Profiles[0]
	role := declarations.Roles[0]
	if profile.AppID != fixtureAppID || profile.AppReleaseDigest != verified.AppReleaseDigest ||
		profile.Key != (layoutroutingsecurityv1.ProfileKey{NamespaceID: fixtureNamespace, LocalKey: "billing.operator"}) {
		t.Fatalf("profile declaration = %#v", profile)
	}
	if role.AppID != fixtureAppID || role.AppReleaseDigest != verified.AppReleaseDigest ||
		role.Key != (layoutroutingsecurityv1.RoleKey{NamespaceID: fixtureNamespace, LocalKey: "billing.operator"}) {
		t.Fatalf("role declaration = %#v", role)
	}
	if profile.BehaviorDigest == "" || role.BehaviorDigest == "" || profile.BehaviorDigest == role.BehaviorDigest {
		t.Fatalf("typed behavior digests = %q, %q", profile.BehaviorDigest, role.BehaviorDigest)
	}
}

func TestVerifyPackageDeclarationsAllowsNoDeclarations(t *testing.T) {
	fixture := newPackageFixture(t)
	_, declarations, err := VerifyPackageDeclarations(fixture.pkg)
	if err != nil {
		t.Fatal(err)
	}
	if declarations.Profiles == nil || declarations.Roles == nil || len(declarations.Profiles) != 0 || len(declarations.Roles) != 0 {
		t.Fatalf("declarations = %#v", declarations)
	}
}

func TestVerifyPackageDeclarationsAllowsSharedBehaviorAcrossDeclarations(t *testing.T) {
	fixture := newPackageFixture(t,
		fixtureDeclarationSpec{componentKind: ProfileComponentKind, namespaceID: fixtureNamespace, localKey: "billing.reader"},
		fixtureDeclarationSpec{componentKind: ProfileComponentKind, namespaceID: fixtureNamespace, localKey: "billing.writer"},
	)
	_, declarations, err := VerifyPackageDeclarations(fixture.pkg)
	if err != nil {
		t.Fatal(err)
	}
	if len(declarations.Profiles) != 2 || declarations.Profiles[0].BehaviorDigest != declarations.Profiles[1].BehaviorDigest {
		t.Fatalf("profile declarations = %#v", declarations.Profiles)
	}
}

func TestVerifyPackageDeclarationsRejectsMalformedDescriptors(t *testing.T) {
	for name, mutate := range map[string]func(*packageFixture, int){
		"format": func(fixture *packageFixture, index int) {
			fixture.pkg.Bundle.Components[index].ComponentFormatVersion = 2
			rehashFixture(t, &fixture.pkg)
		},
		"definition version": func(fixture *packageFixture, index int) {
			fixture.pkg.Bundle.Components[index].DefinitionVersion = "2"
			rehashFixture(t, &fixture.pkg)
		},
		"local dependency": func(fixture *packageFixture, index int) {
			fixture.pkg.Bundle.Components[index].LocalDependencies = []string{fixture.supportComponentID}
			rehashFixture(t, &fixture.pkg)
		},
		"external reference": func(fixture *packageFixture, index int) {
			fixture.pkg.Bundle.Components[index].ExternalComponentRefs = []appauthority.ExternalComponentRef{{DependencyAlias: "other", NamespaceID: fixtureNamespace, ComponentKind: "metadata.object.v1", BusinessKey: "Object"}}
			fixture.pkg.Manifest.Dependencies = []appauthority.Dependency{{Alias: "other", AppID: "io.example.other", MinimumVersionInclusive: "1.0.0", MaximumVersionExclusive: "2.0.0", RequiredCapabilities: []string{}}}
			rehashFixture(t, &fixture.pkg)
		},
		"override policy": func(fixture *packageFixture, index int) {
			fixture.pkg.Bundle.Components[index].OverridePolicy = appauthority.OverridePolicyPreserveAuthorized
			rehashFixture(t, &fixture.pkg)
		},
		"removal policy": func(fixture *packageFixture, index int) {
			fixture.pkg.Bundle.Components[index].RemovalPolicy = appauthority.RemovalPolicyRemoveIfUnreferenced
			rehashFixture(t, &fixture.pkg)
		},
		"invalid typed local key": func(fixture *packageFixture, index int) {
			fixture.pkg.Bundle.Components[index].BusinessKey = "Billing.Operator"
			component := fixture.pkg.Bundle.Components[index]
			component.ComponentID = appauthority.ComponentID(component.NamespaceID, component.ComponentKind, component.BusinessKey)
			fixture.pkg.Bundle.Components[index] = component
			fixture.pkg.ComponentBlobs[index].ComponentID = component.ComponentID
			rehashFixture(t, &fixture.pkg)
		},
	} {
		t.Run(name, func(t *testing.T) {
			fixture := newPackageFixture(t, fixtureDeclarationSpec{componentKind: ProfileComponentKind, namespaceID: fixtureNamespace, localKey: "billing.operator"})
			index := findComponentIndex(t, fixture, ProfileComponentKind, "billing.operator")
			mutate(&fixture, index)
			if _, _, err := VerifyPackageDeclarations(fixture.pkg); err == nil {
				t.Fatal("VerifyPackageDeclarations succeeded")
			}
		})
	}
}

func TestVerifyPackageDeclarationsRejectsMalformedBehavior(t *testing.T) {
	fixture := newPackageFixture(t, fixtureDeclarationSpec{componentKind: RoleComponentKind, namespaceID: fixtureNamespace, localKey: "billing.operator"})
	index := findComponentIndex(t, fixture, RoleComponentKind, "billing.operator")
	setComponentBehavior(t, &fixture, index, []byte(`{"schema_version":"nexuscrm.app-identity-key-declaration/v1","extra":"x"}`))
	if _, _, err := VerifyPackageDeclarations(fixture.pkg); err == nil {
		t.Fatal("VerifyPackageDeclarations succeeded")
	}
}

func TestVerifyPackageDeclarationsDoesNotTreatUnknownComponentsAsDeclarations(t *testing.T) {
	fixture := newPackageFixture(t, fixtureDeclarationSpec{componentKind: "metadata.object.v1", namespaceID: fixtureNamespace, localKey: "Object"})
	behavior := []byte(`{"fields":["Name"]}`)
	index := 0
	digest, err := appauthority.ComponentDigest("metadata.object.v1", 1, behavior)
	if err != nil {
		t.Fatal(err)
	}
	fixture.pkg.Bundle.Components[index].ContentDigest = digest
	fixture.pkg.ComponentBlobs[index].CanonicalBehavior = behavior
	rehashFixture(t, &fixture.pkg)
	_, declarations, err := VerifyPackageDeclarations(fixture.pkg)
	if err != nil {
		t.Fatal(err)
	}
	if len(declarations.Profiles) != 0 || len(declarations.Roles) != 0 {
		t.Fatalf("declarations = %#v", declarations)
	}
}

func TestVerifyPackageDeclarationsCopiesBehaviorFactsFromDescriptor(t *testing.T) {
	fixture := newPackageFixture(t, fixtureDeclarationSpec{componentKind: ProfileComponentKind, namespaceID: fixtureNamespace, localKey: "billing.operator"})
	_, declarations, err := VerifyPackageDeclarations(fixture.pkg)
	if err != nil {
		t.Fatal(err)
	}
	index := findComponentIndex(t, fixture, ProfileComponentKind, "billing.operator")
	if declarations.Profiles[0].DeclarationComponentID != fixture.pkg.Bundle.Components[index].ComponentID ||
		!bytes.Equal(fixture.pkg.ComponentBlobs[index].CanonicalBehavior, []byte(`{"schema_version":"nexuscrm.app-identity-key-declaration/v1"}`)) {
		t.Fatalf("declaration = %#v", declarations.Profiles[0])
	}
}
