package appidentitykeydeclarationv1

import (
	"sort"
	"testing"

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

const (
	fixtureAppID     = "io.example.app"
	fixtureNamespace = "io.example.app"
)

type fixtureDeclarationSpec struct {
	componentKind string
	namespaceID   string
	localKey      string
}

type packageFixture struct {
	pkg                appauthority.Package
	supportComponentID string
}

func newPackageFixture(t *testing.T, declarations ...fixtureDeclarationSpec) packageFixture {
	t.Helper()
	behavior, err := CanonicalBehaviorBytes(Behavior{SchemaVersion: BehaviorSchemaVersion})
	if err != nil {
		t.Fatal(err)
	}
	type componentBlobPair struct {
		component appauthority.Component
		behavior  []byte
	}
	pairs := make([]componentBlobPair, 0, len(declarations)+1)
	ownedNamespaces := map[string]bool{fixtureNamespace: true}
	supportBehavior := []byte(`{"fields":["Name"]}`)
	supportDigest, supportDigestErr := appauthority.ComponentDigest("metadata.object.v1", 1, supportBehavior)
	if supportDigestErr != nil {
		t.Fatal(supportDigestErr)
	}
	support := appauthority.Component{
		ComponentKind:          "metadata.object.v1",
		ComponentFormatVersion: 1,
		NamespaceID:            fixtureNamespace,
		BusinessKey:            "IdentityTarget",
		DefinitionVersion:      "1",
		LocalDependencies:      []string{},
		ExternalComponentRefs:  []appauthority.ExternalComponentRef{},
		OverridePolicy:         appauthority.OverridePolicyReplace,
		RemovalPolicy:          appauthority.RemovalPolicyBlockIfReferencedOrOverridden,
		ContentDigest:          supportDigest,
	}
	support.ComponentID = appauthority.ComponentID(support.NamespaceID, support.ComponentKind, support.BusinessKey)
	pairs = append(pairs, componentBlobPair{component: support, behavior: supportBehavior})
	for _, declaration := range declarations {
		ownedNamespaces[declaration.namespaceID] = true
		digest, digestErr := appauthority.ComponentDigest(declaration.componentKind, ComponentFormatVersion, behavior)
		if digestErr != nil {
			t.Fatal(digestErr)
		}
		component := appauthority.Component{
			ComponentKind:          declaration.componentKind,
			ComponentFormatVersion: ComponentFormatVersion,
			NamespaceID:            declaration.namespaceID,
			BusinessKey:            declaration.localKey,
			DefinitionVersion:      DefinitionVersion,
			LocalDependencies:      []string{},
			ExternalComponentRefs:  []appauthority.ExternalComponentRef{},
			OverridePolicy:         appauthority.OverridePolicyReplace,
			RemovalPolicy:          appauthority.RemovalPolicyBlockIfReferencedOrOverridden,
			ContentDigest:          digest,
		}
		component.ComponentID = appauthority.ComponentID(component.NamespaceID, component.ComponentKind, component.BusinessKey)
		pairs = append(pairs, componentBlobPair{component: component, behavior: append([]byte(nil), behavior...)})
	}
	sort.SliceStable(pairs, func(left, right int) bool {
		return pairs[left].component.ComponentID < pairs[right].component.ComponentID
	})
	components := make([]appauthority.Component, len(pairs))
	blobs := make([]appauthority.ComponentBlob, len(pairs))
	for index, pair := range pairs {
		components[index] = pair.component
		blobs[index] = appauthority.ComponentBlob{ComponentID: pair.component.ComponentID, CanonicalBehavior: append([]byte(nil), pair.behavior...)}
	}
	namespaces := make([]string, 0, len(ownedNamespaces))
	for namespaceID := range ownedNamespaces {
		namespaces = append(namespaces, namespaceID)
	}
	sort.Strings(namespaces)
	manifest := appauthority.AppManifest{
		SchemaVersion:              appauthority.AppManifestSchemaVersion,
		AppID:                      fixtureAppID,
		PublisherID:                "io.example",
		SemanticVersion:            "1.0.0",
		MetadataBundleDigest:       "sha256:0000000000000000000000000000000000000000000000000000000000000000",
		Dependencies:               []appauthority.Dependency{},
		OwnedNamespaces:            namespaces,
		RequiredProtocols:          []appauthority.ProtocolRequirement{},
		RequiredEngineCapabilities: []string{},
		ProvidedCapabilities:       []appauthority.ProvidedCapability{},
		FrontendCompatibility:      appauthority.FrontendCompatibility{Mode: appauthority.FrontendModeNone},
	}
	fixture := packageFixture{pkg: appauthority.Package{
		Manifest:       manifest,
		Bundle:         appauthority.MetadataBundle{SchemaVersion: appauthority.MetadataBundleSchemaVersion, Components: components},
		ComponentBlobs: blobs,
	}, supportComponentID: support.ComponentID}
	rehashFixture(t, &fixture.pkg)
	return fixture
}

func rehashFixture(t *testing.T, pkg *appauthority.Package) {
	t.Helper()
	digest, err := appauthority.MetadataBundleDigest(pkg.Bundle, pkg.Manifest)
	if err != nil {
		t.Fatal(err)
	}
	pkg.Manifest.MetadataBundleDigest = digest
}

func findComponentIndex(t *testing.T, fixture packageFixture, componentKind, localKey string) int {
	t.Helper()
	for index, component := range fixture.pkg.Bundle.Components {
		if component.ComponentKind == componentKind && component.BusinessKey == localKey {
			return index
		}
	}
	t.Fatalf("component %s/%s not found", componentKind, localKey)
	return -1
}

func setComponentBehavior(t *testing.T, fixture *packageFixture, index int, behavior []byte) {
	t.Helper()
	component := fixture.pkg.Bundle.Components[index]
	digest, err := appauthority.ComponentDigest(component.ComponentKind, component.ComponentFormatVersion, behavior)
	if err != nil {
		t.Fatal(err)
	}
	component.ContentDigest = digest
	fixture.pkg.Bundle.Components[index] = component
	fixture.pkg.ComponentBlobs[index].CanonicalBehavior = append([]byte(nil), behavior...)
	rehashFixture(t, &fixture.pkg)
}
