package catalogreconcile

import (
	"encoding/json"
	"os"
	"reflect"
	"sort"
	"strings"
	"testing"

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

func TestReleaseHostCompatibilityIsCanonicalSupportedCompilerAndSourceSubset(t *testing.T) {
	t.Parallel()
	type component struct {
		ComponentKind          string `json:"componentKind"`
		ComponentFormatVersion int    `json:"componentFormatVersion"`
	}
	type manifest struct {
		Schema         string      `json:"schema"`
		ComponentKinds []component `json:"componentKinds"`
	}
	raw, err := os.ReadFile("host-compatibility.json")
	if err != nil {
		t.Fatal(err)
	}
	var got manifest
	if err := json.Unmarshal(raw, &got); err != nil {
		t.Fatal(err)
	}
	if got.Schema != "nexuscrm.catalog-host-compatibility/v1" || len(got.ComponentKinds) == 0 {
		t.Fatalf("invalid release compatibility manifest: %#v", got)
	}
	names := make([]string, 0, len(got.ComponentKinds))
	seen := map[string]struct{}{}
	ownedDirectKinds := map[string]struct{}{
		approvalReleaseComponentKind:   {},
		recordViewReleaseComponentKind: {},
		reportReleaseComponentKind:     {},
		commandReleaseComponentKind:    {},
	}
	// These kinds are accepted so Catalog can preserve and structurally verify
	// sealed source declarations. They are deliberately not compiler-owned or
	// serving component kinds.
	sourceOnlyKinds := map[string]struct{}{
		appidentitykeydeclarationv1.ProfileComponentKind: {},
		appidentitykeydeclarationv1.RoleComponentKind:    {},
	}
	// These component kinds are installed by L0 engine registrations rather
	// than Controlplane's built-in pack compiler. Catalog must preserve and
	// structurally verify them because the composed L0 Engine advertises the
	// registration as part of the same release boundary.
	externallyRegisteredKinds := map[string]struct{}{
		"metadata.pack.custom-permission.v1": {},
	}
	for _, item := range got.ComponentKinds {
		if item.ComponentFormatVersion != 1 || !strings.HasSuffix(item.ComponentKind, ".v1") {
			t.Fatalf("invalid component support fact: %#v", item)
		}
		if _, duplicate := seen[item.ComponentKind]; duplicate {
			t.Fatalf("duplicate component support fact %q", item.ComponentKind)
		}
		seen[item.ComponentKind] = struct{}{}
		if _, owned := ownedDirectKinds[item.ComponentKind]; owned {
			names = append(names, item.ComponentKind)
			continue
		}
		if _, sourceOnly := sourceOnlyKinds[item.ComponentKind]; sourceOnly {
			names = append(names, item.ComponentKind)
			continue
		}
		if _, externallyRegistered := externallyRegisteredKinds[item.ComponentKind]; externallyRegistered {
			names = append(names, item.ComponentKind)
			continue
		}
		if !strings.HasPrefix(item.ComponentKind, "metadata.pack.") {
			t.Fatalf("release manifest claims an unsupported Catalog component kind %q", item.ComponentKind)
		}
		name := strings.TrimSuffix(strings.TrimPrefix(item.ComponentKind, "metadata.pack."), ".v1")
		kind := packs.ComponentKind(strings.ReplaceAll(name, "-", "_"))
		if !packs.IsAppPackInstallableComponentKind(kind) {
			t.Fatalf("release manifest claims unsupported AppPackage component kind %q", item.ComponentKind)
		}
		names = append(names, item.ComponentKind)
	}
	if !sort.StringsAreSorted(names) {
		t.Fatalf("component support facts are not canonical: %v", names)
	}
	want := make([]component, 0, len(sourceOnlyKinds))
	for _, kind := range packs.AppPackInstallableComponentKinds() {
		name := strings.ReplaceAll(string(kind), "_", "-")
		want = append(want, component{
			ComponentKind:          "metadata.pack." + name + ".v1",
			ComponentFormatVersion: 1,
		})
	}
	for kind := range ownedDirectKinds {
		want = append(want, component{ComponentKind: kind, ComponentFormatVersion: 1})
	}
	for kind := range sourceOnlyKinds {
		want = append(want, component{ComponentKind: kind, ComponentFormatVersion: 1})
	}
	for kind := range externallyRegisteredKinds {
		want = append(want, component{ComponentKind: kind, ComponentFormatVersion: 1})
	}
	sort.Slice(want, func(i, j int) bool { return want[i].ComponentKind < want[j].ComponentKind })
	if !reflect.DeepEqual(got.ComponentKinds, want) {
		t.Fatalf("release compatibility manifest drifted from compiler authority:\ngot  %#v\nwant %#v", got.ComponentKinds, want)
	}
}

func TestCatalogPublisherNeverBlessesAnUnverifiedExistingDigest(t *testing.T) {
	workflow, err := os.ReadFile("../.github/workflows/trusted-publish-catalog-reconcile-image.yml")
	if err != nil {
		t.Fatal(err)
	}
	text := string(workflow)
	for _, required := range []string{
		"if: ${{ needs.candidate.outputs.exists != 'true' }}",
		"cosign attest --yes",
		"cosign verify-attestation",
		"--insecure-ignore-tlog",
		"pending-${{ env.SOURCE_SHA }}-${{ github.run_id }}-${{ github.run_attempt }}",
		"docker buildx imagetools create --tag \"$image_tag\"",
		"Reverify immutable existing release without signing authority",
	} {
		if !strings.Contains(text, required) {
			t.Fatalf("Catalog publisher lost provenance invariant %q", required)
		}
	}
	publish := strings.Index(text, "- name: Publish pending exact-source Catalog reconcile image")
	sign := strings.Index(text, "- name: Sign exact digest and canonical Catalog provenance")
	verify := strings.Index(text, "- name: Verify owner signature and exact provenance before tag commit")
	commit := strings.Index(text, "- name: Commit independently verified Catalog source tag")
	if publish < 0 || sign <= publish || verify <= sign || commit <= verify {
		t.Fatal("Catalog must build a pending digest, sign and verify it, then commit the stable source tag")
	}
	if strings.Contains(text, "actions/attest") || strings.Contains(text, "gh attestation") {
		t.Fatal("Catalog publisher must not depend on unavailable GitHub private-repository Artifact Attestations")
	}
	copyCompatibility := strings.Index(text, `cp catalogreconcile/host-compatibility.json`)
	verifyCompatibility := strings.Index(text, `cmp catalogreconcile/host-compatibility.json`)
	if copyCompatibility < 0 || verifyCompatibility <= copyCompatibility || publish <= copyCompatibility || sign <= verifyCompatibility {
		t.Fatal("Catalog-owned compatibility must be embedded and checked before the candidate reaches the signer")
	}
}
