package layoutbundleslotv1

import (
	"sort"
	"strconv"
	"testing"

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

const (
	fixtureAppID     = "io.example.app"
	fixtureNamespace = "io.example.app"
	fixtureLocalKey  = layoutbundlekeyv1.LocalComponentKey("key256:1111111111111111111111111111111111111111111111111111111111111111")
)

type fixtureComponent struct {
	component appauthority.Component
	behavior  []byte
}

type slotPackageFixture struct {
	pkg       appauthority.Package
	slotIndex int
}

func newSlotPackageFixture(t *testing.T, definitionVersion uint64, targetBusinessKey string, previousTarget *string) slotPackageFixture {
	t.Helper()
	targetBehavior := []byte(`{"fields":["Name"]}`)
	targetDigest, err := appauthority.ComponentDigest("metadata.object.v1", 1, targetBehavior)
	if err != nil {
		t.Fatal(err)
	}
	target := appauthority.Component{
		ComponentKind: "metadata.object.v1", ComponentFormatVersion: 1,
		NamespaceID: fixtureNamespace, BusinessKey: targetBusinessKey, DefinitionVersion: "1",
		LocalDependencies: []string{}, ExternalComponentRefs: []appauthority.ExternalComponentRef{},
		OverridePolicy: appauthority.OverridePolicyReplace,
		RemovalPolicy:  appauthority.RemovalPolicyBlockIfReferencedOrOverridden,
		ContentDigest:  targetDigest,
	}
	target.ComponentID = appauthority.ComponentID(target.NamespaceID, target.ComponentKind, target.BusinessKey)
	behavior, err := CanonicalBehaviorBytes(Behavior{
		SchemaVersion: BehaviorSchemaVersion, LocalComponentKey: fixtureLocalKey,
		TargetComponentID: target.ComponentID, PreviousTargetComponentID: cloneStringPointer(previousTarget),
	})
	if err != nil {
		t.Fatal(err)
	}
	slotDigest, err := appauthority.ComponentDigest(ComponentKind, ComponentFormatVersion, behavior)
	if err != nil {
		t.Fatal(err)
	}
	slot := appauthority.Component{
		ComponentKind: ComponentKind, ComponentFormatVersion: ComponentFormatVersion,
		NamespaceID: fixtureNamespace, BusinessKey: string(fixtureLocalKey), DefinitionVersion: strconv.FormatUint(definitionVersion, 10),
		LocalDependencies: []string{target.ComponentID}, ExternalComponentRefs: []appauthority.ExternalComponentRef{},
		OverridePolicy: appauthority.OverridePolicyReplace,
		RemovalPolicy:  appauthority.RemovalPolicyBlockIfReferencedOrOverridden,
		ContentDigest:  slotDigest,
	}
	slot.ComponentID = appauthority.ComponentID(slot.NamespaceID, slot.ComponentKind, slot.BusinessKey)
	components := []fixtureComponent{{component: target, behavior: targetBehavior}, {component: slot, behavior: behavior}}
	sort.Slice(components, func(left, right int) bool {
		return components[left].component.ComponentID < components[right].component.ComponentID
	})
	bundleComponents := make([]appauthority.Component, len(components))
	blobs := make([]appauthority.ComponentBlob, len(components))
	slotIndex := -1
	for index, value := range components {
		bundleComponents[index] = value.component
		blobs[index] = appauthority.ComponentBlob{ComponentID: value.component.ComponentID, CanonicalBehavior: append([]byte(nil), value.behavior...)}
		if value.component.ComponentKind == ComponentKind {
			slotIndex = index
		}
	}
	manifest := appauthority.AppManifest{
		SchemaVersion: appauthority.AppManifestSchemaVersion,
		AppID:         fixtureAppID, PublisherID: "io.example", SemanticVersion: "1.0.0",
		MetadataBundleDigest: "sha256:0000000000000000000000000000000000000000000000000000000000000000",
		Dependencies:         []appauthority.Dependency{}, OwnedNamespaces: []string{fixtureNamespace},
		RequiredProtocols: []appauthority.ProtocolRequirement{}, RequiredEngineCapabilities: []string{},
		ProvidedCapabilities:  []appauthority.ProvidedCapability{},
		FrontendCompatibility: appauthority.FrontendCompatibility{Mode: appauthority.FrontendModeNone},
	}
	bundle := appauthority.MetadataBundle{SchemaVersion: appauthority.MetadataBundleSchemaVersion, Components: bundleComponents}
	pkg := appauthority.Package{Manifest: manifest, Bundle: bundle, ComponentBlobs: blobs}
	rehashFixture(t, &pkg)
	return slotPackageFixture{pkg: pkg, slotIndex: slotIndex}
}

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 setSlotBehavior(t *testing.T, fixture *slotPackageFixture, behavior []byte) {
	t.Helper()
	digest, err := appauthority.ComponentDigest(ComponentKind, ComponentFormatVersion, behavior)
	if err != nil {
		t.Fatal(err)
	}
	fixture.pkg.ComponentBlobs[fixture.slotIndex].CanonicalBehavior = append([]byte(nil), behavior...)
	fixture.pkg.Bundle.Components[fixture.slotIndex].ContentDigest = digest
	rehashFixture(t, &fixture.pkg)
}

func fixtureDeclaration(t *testing.T, definitionVersion uint64, targetBusinessKey string, previousTarget *string) (slotPackageFixture, Declaration) {
	t.Helper()
	fixture := newSlotPackageFixture(t, definitionVersion, targetBusinessKey, previousTarget)
	_, declarations, err := VerifyPackageDeclarations(fixture.pkg)
	if err != nil {
		t.Fatal(err)
	}
	if len(declarations) != 1 {
		t.Fatalf("declaration count = %d", len(declarations))
	}
	return fixture, declarations[0]
}

func pointer(value string) *string { return &value }
