package applayoutbundleauthorityv1

import (
	"math"
	"strings"
	"testing"

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

func TestValidateEvidenceAcceptsCompleteExactValue(t *testing.T) {
	if err := ValidateEvidence(validEvidence()); err != nil {
		t.Fatal(err)
	}
}

func TestValidateEvidenceRejectsIncompleteOrNonCanonicalValue(t *testing.T) {
	tests := map[string]struct {
		mutate        func(*Evidence)
		errorContains string
	}{
		"evidence version": {
			mutate:        func(value *Evidence) { value.EvidenceVersion = "nexuscrm.layout-bundle-app-authority-evidence/v2" },
			errorContains: "EvidenceVersion",
		},
		"blank source org": {
			mutate:        func(value *Evidence) { value.SourceCatalogSnapshot.OrgID = "" },
			errorContains: "SourceCatalogSnapshot",
		},
		"zero catalog revision": {
			mutate:        func(value *Evidence) { value.SourceCatalogSnapshot.CatalogRevision = 0 },
			errorContains: "CatalogRevision",
		},
		"uppercase catalog digest": {
			mutate:        func(value *Evidence) { value.SourceCatalogSnapshot.CatalogSnapshotDigest = digest("A") },
			errorContains: "CatalogSnapshotDigest",
		},
		"zero schema generation": {
			mutate:        func(value *Evidence) { value.SourceCatalogSnapshot.SchemaGeneration = 0 },
			errorContains: "SchemaGeneration",
		},
		"zero engine generation": {
			mutate:        func(value *Evidence) { value.SourceCatalogSnapshot.EngineGeneration = 0 },
			errorContains: "EngineGeneration",
		},
		"bundle key version": {
			mutate:        func(value *Evidence) { value.BundleKey.BundleKeyVersion = "nexuscrm.layout-bundle-key/v2" },
			errorContains: "BundleKeyVersion",
		},
		"tenant owner": {
			mutate:        func(value *Evidence) { value.BundleKey.SlotOwnerKind = layoutbundlekeyv1.SlotOwnerKindTenant },
			errorContains: "SlotOwnerKind",
		},
		"system owner": {
			mutate:        func(value *Evidence) { value.BundleKey.SlotOwnerKind = layoutbundlekeyv1.SlotOwnerKindSystem },
			errorContains: "SlotOwnerKind",
		},
		"uppercase app namespace": {
			mutate:        func(value *Evidence) { value.BundleKey.SlotOwnerNamespace = "io.example.App" },
			errorContains: "SlotOwnerNamespace",
		},
		"short app namespace": {
			mutate:        func(value *Evidence) { value.BundleKey.SlotOwnerNamespace = "io.example" },
			errorContains: "SlotOwnerNamespace",
		},
		"invalid local key": {
			mutate:        func(value *Evidence) { value.BundleKey.LocalComponentKey = "key256:" },
			errorContains: "LocalComponentKey",
		},
		"uppercase installation": {
			mutate:        func(value *Evidence) { value.AppInstallationID = "01890F9E-7B2A-7000-8000-000000000001" },
			errorContains: "AppInstallationID",
		},
		"uuid version 4 installation": {
			mutate:        func(value *Evidence) { value.AppInstallationID = "01890f9e-7b2a-4000-8000-000000000001" },
			errorContains: "AppInstallationID",
		},
		"invalid app release digest": {
			mutate:        func(value *Evidence) { value.AppReleaseDigest = "sha256:1234" },
			errorContains: "AppReleaseDigest",
		},
		"uppercase component digest": {
			mutate:        func(value *Evidence) { value.DeclarationComponentID = digest("B") },
			errorContains: "DeclarationComponentID",
		},
		"zero definition version": {
			mutate:        func(value *Evidence) { value.DefinitionVersion = 0 },
			errorContains: "DefinitionVersion",
		},
		"invalid revision digest": {
			mutate:        func(value *Evidence) { value.LayoutBundleSlotRevisionDigest = "" },
			errorContains: "LayoutBundleSlotRevisionDigest",
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			value := validEvidence()
			test.mutate(&value)
			err := ValidateEvidence(value)
			if err == nil || !strings.Contains(err.Error(), test.errorContains) {
				t.Fatalf("ValidateEvidence error = %v", err)
			}
			if bytes, err := CanonicalEvidenceBytes(value); err == nil || bytes != nil {
				t.Fatalf("invalid evidence acquired bytes %x, err=%v", bytes, err)
			}
			if digest, err := EvidenceDigest(value); err == nil || digest != "" {
				t.Fatalf("invalid evidence acquired digest %q, err=%v", digest, err)
			}
		})
	}
}

func TestEvidenceDigestCommitsEveryVariableField(t *testing.T) {
	base := validEvidence()
	baseDigest, err := EvidenceDigest(base)
	if err != nil {
		t.Fatal(err)
	}
	mutations := map[string]func(*Evidence){
		"SourceCatalogSnapshot.OrgID":                 func(value *Evidence) { value.SourceCatalogSnapshot.OrgID = "org-2" },
		"SourceCatalogSnapshot.CatalogRevision":       func(value *Evidence) { value.SourceCatalogSnapshot.CatalogRevision++ },
		"SourceCatalogSnapshot.CatalogSnapshotDigest": func(value *Evidence) { value.SourceCatalogSnapshot.CatalogSnapshotDigest = digest("9") },
		"SourceCatalogSnapshot.SchemaGeneration":      func(value *Evidence) { value.SourceCatalogSnapshot.SchemaGeneration++ },
		"SourceCatalogSnapshot.EngineGeneration":      func(value *Evidence) { value.SourceCatalogSnapshot.EngineGeneration++ },
		"BundleKey.SlotOwnerNamespace":                func(value *Evidence) { value.BundleKey.SlotOwnerNamespace = "io.example.other" },
		"BundleKey.LocalComponentKey": func(value *Evidence) {
			value.BundleKey.LocalComponentKey = layoutbundlekeyv1.LocalComponentKey("key256:" + strings.Repeat("8", 64))
		},
		"AppInstallationID":              func(value *Evidence) { value.AppInstallationID = "01890f9e-7b2a-7000-8000-000000000002" },
		"AppReleaseDigest":               func(value *Evidence) { value.AppReleaseDigest = digest("7") },
		"DeclarationComponentID":         func(value *Evidence) { value.DeclarationComponentID = digest("6") },
		"DefinitionVersion":              func(value *Evidence) { value.DefinitionVersion++ },
		"LayoutBundleSlotRevisionDigest": func(value *Evidence) { value.LayoutBundleSlotRevisionDigest = digest("0") },
	}
	for name, mutate := range mutations {
		t.Run(name, func(t *testing.T) {
			changed := base
			mutate(&changed)
			changedDigest, err := EvidenceDigest(changed)
			if err != nil {
				t.Fatal(err)
			}
			if changedDigest == baseDigest {
				t.Fatal("field change did not change evidence digest")
			}
		})
	}
}

func TestCanonicalUnsignedLEB128Boundaries(t *testing.T) {
	value := validEvidence()
	value.SourceCatalogSnapshot.CatalogRevision = 127
	value.SourceCatalogSnapshot.SchemaGeneration = 128
	value.SourceCatalogSnapshot.EngineGeneration = 16_384
	value.DefinitionVersion = math.MaxUint64
	if _, err := CanonicalEvidenceBytes(value); err != nil {
		t.Fatal(err)
	}
}

func validEvidence() Evidence {
	return Evidence{
		EvidenceVersion: EvidenceVersion,
		SourceCatalogSnapshot: catalogpublicationv1.CatalogSnapshotBinding{
			OrgID: "org-1", CatalogRevision: 1,
			CatalogSnapshotDigest: digest("1"), SchemaGeneration: 2, EngineGeneration: 3,
		},
		BundleKey: BundleKey{
			BundleKeyVersion:   layoutbundlekeyv1.BundleKeyVersion,
			SlotOwnerKind:      layoutbundlekeyv1.SlotOwnerKindApp,
			SlotOwnerNamespace: "io.example.app",
			LocalComponentKey:  layoutbundlekeyv1.LocalComponentKey("key256:" + strings.Repeat("2", 64)),
		},
		AppInstallationID: "01890f9e-7b2a-7000-8000-000000000001",
		AppReleaseDigest:  digest("3"), DeclarationComponentID: digest("4"),
		DefinitionVersion: 1, LayoutBundleSlotRevisionDigest: digest("5"),
	}
}

func digest(character string) string { return "sha256:" + strings.Repeat(character, 64) }
