package boundary_test

import (
	"go/ast"
	"go/parser"
	"go/token"
	"path/filepath"
	"sort"
	"strconv"
	"testing"
)

// L0-D D6 boundary guard: the platform bootstrap profile surface stays
// platform-only. D2 (profile permission seeding) and D3 (built-in profile
// catalog) were deliberate NON-carves in L0-D — the platform owns exactly two
// built-in profiles (System Administrator, Standard User) and seeds defaults
// only for them; every OTHER profile is import-/admin-owned and receives no
// platform default. This guard is a POSITIVE ALLOWLIST (not a count-0 ban): it
// fails when a NEW profile key is added to either bootstrap surface, which is
// how Salesforce standard-profile catalog regrowth (Marketing User, Contract
// Manager, ...) would sneak back into app-blind L0.

// platformBootstrapProfileKeys is the exact set of built-in profile identifiers
// the platform bootstrap owns. Both bootstrap surfaces must key on exactly this
// set — no more.
var platformBootstrapProfileKeys = map[string]struct{}{
	"ProfileSystemAdmin":  {},
	"ProfileStandardUser": {},
}

// TestProfilePermRulesRemainPlatformBootstrapOnly asserts the profilePermRules
// seeding matrix in permission_seeder.go keys on ONLY the two platform built-in
// profiles. Any NEW map key (an app/SF profile gaining a platform-seeded
// default) fails the guard.
func TestProfilePermRulesRemainPlatformBootstrapOnly(t *testing.T) {
	root := repoRoot(t)
	rel := "nexuscrm-permissions/permission_seeder.go"
	keys := mapVarKeyIdents(t, filepath.Join(root, filepath.FromSlash(rel)), "profilePermRules")
	if len(keys) == 0 {
		t.Fatalf("%s: could not find any keys for var profilePermRules; the guard cannot verify the bootstrap allowlist", rel)
	}
	assertProfileKeysAllowlisted(t, rel, "profilePermRules", keys)
}

// TestBuiltInProfileCatalogStaysPlatformOnly asserts the built-in profile
// catalog in shared/pkg/constants/profiles.go — the PlatformDefaultProfileIDs
// set that reconcile/verify gate on — lists ONLY the two platform built-in
// profiles. An added app/SF profile catalog entry fails the guard.
func TestBuiltInProfileCatalogStaysPlatformOnly(t *testing.T) {
	root := repoRoot(t)
	rel := "shared/pkg/constants/profiles.go"
	keys := mapVarKeyIdents(t, filepath.Join(root, filepath.FromSlash(rel)), "PlatformDefaultProfileIDs")
	if len(keys) == 0 {
		t.Fatalf("%s: could not find any keys for var PlatformDefaultProfileIDs; the guard cannot verify the catalog allowlist", rel)
	}
	assertProfileKeysAllowlisted(t, rel, "PlatformDefaultProfileIDs", keys)
}

// TestProfileBootstrapKeyExtractorFindsExpectedKeys documents the AST key
// extractor: it collects composite-literal map keys as their trailing
// identifier name, whether the key is a bare ident (ProfileSystemAdmin) or a
// package selector (constants.ProfileSystemAdmin).
func TestProfileBootstrapKeyExtractorFindsExpectedKeys(t *testing.T) {
	src := "package sample\n\nvar m = map[string]int{\n\tconstants.ProfileSystemAdmin: 1,\n\tProfileStandardUser:        2,\n}\n"
	keys := mapVarKeyIdentsFromSrc(t, "sample.go", src, "m")
	sort.Strings(keys)
	want := []string{"ProfileStandardUser", "ProfileSystemAdmin"}
	if len(keys) != len(want) || keys[0] != want[0] || keys[1] != want[1] {
		t.Fatalf("extractor keys = %v, want %v", keys, want)
	}
}

func assertProfileKeysAllowlisted(t *testing.T, rel, varName string, keys []string) {
	t.Helper()
	for _, key := range keys {
		if _, ok := platformBootstrapProfileKeys[key]; !ok {
			t.Errorf("%s: %s declares profile key %q, which is not a platform built-in profile; platform bootstrap must stay limited to the built-in profiles — an app/SF profile catalog entry belongs to import/admin ownership, not app-blind L0", rel, varName, key)
		}
	}
}

// mapVarKeyIdents parses the file at path and returns the trailing identifier
// name of every composite-literal key of the named map variable.
func mapVarKeyIdents(t *testing.T, path, varName string) []string {
	t.Helper()
	return mapVarKeyIdentsFromSrc(t, path, readFile(t, path), varName)
}

func mapVarKeyIdentsFromSrc(t *testing.T, name, src, varName string) []string {
	t.Helper()
	fset := token.NewFileSet()
	parsed, err := parser.ParseFile(fset, name, src, 0)
	if err != nil {
		t.Fatalf("parse %s: %v", name, err)
	}

	var keys []string
	ast.Inspect(parsed, func(n ast.Node) bool {
		vs, ok := n.(*ast.ValueSpec)
		if !ok {
			return true
		}
		for i, ident := range vs.Names {
			if ident.Name != varName || i >= len(vs.Values) {
				continue
			}
			lit, ok := vs.Values[i].(*ast.CompositeLit)
			if !ok {
				continue
			}
			for _, elt := range lit.Elts {
				kv, ok := elt.(*ast.KeyValueExpr)
				if !ok {
					continue
				}
				if key := trailingIdentName(kv.Key); key != "" {
					keys = append(keys, key)
				}
			}
		}
		return true
	})
	return keys
}

// trailingIdentName returns the comparison name for a map key. It handles the
// three key forms that could add a profile to a bootstrap surface:
//   - a bare identifier (ProfileSystemAdmin) -> "ProfileSystemAdmin"
//   - a package selector (constants.ProfileSystemAdmin) -> "ProfileSystemAdmin"
//   - a raw string literal ("acme_sales") -> the unquoted value, so a regression
//     that hardcodes a profile id instead of referencing the platform constant
//     is still caught by the allowlist.
//
// Any other key form yields a synthetic non-empty marker so it is treated as a
// non-allowlisted key (fails closed) rather than silently skipped.
func trailingIdentName(expr ast.Expr) string {
	switch e := expr.(type) {
	case *ast.Ident:
		return e.Name
	case *ast.SelectorExpr:
		return e.Sel.Name
	case *ast.BasicLit:
		if e.Kind == token.STRING {
			if unquoted, err := strconv.Unquote(e.Value); err == nil {
				return unquoted
			}
			return e.Value
		}
		return "<literal-key>"
	}
	return "<unrecognized-key>"
}
