package packs

import (
	"os"
	"strings"
	"testing"
)

// TestPermissionsRuntime_NoLegacyProfilePermissionWriter enforces the
// "exactly one active permission writer/source for system grants" invariant
// at the runtime layer.
//
// After the PR that introduced data-specs/2026-04-23d-migrate-profilepermission-to-junction.md,
// the _System_ProfilePermission table is upgrade-debt — migrated exactly once
// via that one-shot spec — and must not be read from by any runtime seeder
// or boot path. Re-introducing a runtime read into the junction (whether via
// ON DUPLICATE KEY UPDATE or any UPDATE form) would make the legacy table a
// live overwriting writer against the junction SSoT and reproduce the drift
// scenario that broke admin revocations in #1806 / PR #1831 review.
//
// This test is the grep-style evidence requested in the review: it asserts
// that permissions.go contains no runtime reference to the legacy table or
// its constants, only comment-level pointers. If this test fails, read
// docs/lessons/2026-04-23-permission-ssot-cutover.md F5 and L11 before
// "fixing" it — the fix almost certainly wants to go into a one-time
// additive-only data-spec, not back into the runtime seeder.
func TestPermissionsRuntime_NoLegacyProfilePermissionWriter(t *testing.T) {
	src, err := os.ReadFile("permissions.go")
	if err != nil {
		t.Fatalf("read permissions.go: %v", err)
	}

	// Strip line comments (// ...) and block comments (/* ... */) so the
	// remaining text is executable code only. Allowed comment-level
	// references: the directive comment pointing readers to the
	// replacement data-spec. Disallowed: any code use of the legacy
	// symbols.
	stripped := stripGoComments(string(src))

	forbidden := []string{
		// Go constants that name the legacy table and its columns.
		"TableProfilePermission",
		"FieldSysProfilePermission_",
		// Raw string forms a future engineer might inline instead of the constant.
		"_System_ProfilePermission",
		// The exact SQL clause that made Batch 3 an overwriting writer.
		// If any runtime path writes the junction via this clause it will
		// by definition fight admin intent. Use INSERT IGNORE in specs.
		"ON DUPLICATE KEY UPDATE `IsEnabled`",
	}

	for _, needle := range forbidden {
		if strings.Contains(stripped, needle) {
			t.Errorf("permissions.go runtime code must not reference %q — legacy ProfilePermission data is migrated exactly once via data-specs/2026-04-23d-migrate-profilepermission-to-junction.md; re-introducing a runtime reader/writer makes the legacy table a live overwriting source against the junction SSoT (see lessons F5/L11 before attempting a fix).", needle)
		}
	}
}

// stripGoComments removes // line comments and /* ... */ block comments from
// Go source. Not a full Go parser — good enough for text-level invariants.
// Preserves string literals' bodies (a // inside a string literal is not a
// comment) by scanning char-by-char with a small state machine.
func stripGoComments(s string) string {
	var b strings.Builder
	i := 0
	n := len(s)
	for i < n {
		c := s[i]
		// Start of line comment
		if c == '/' && i+1 < n && s[i+1] == '/' {
			j := strings.IndexByte(s[i:], '\n')
			if j == -1 {
				break
			}
			i += j
			continue
		}
		// Start of block comment
		if c == '/' && i+1 < n && s[i+1] == '*' {
			k := strings.Index(s[i+2:], "*/")
			if k == -1 {
				break
			}
			i += 2 + k + 2
			continue
		}
		// String literal — consume through closing quote (respecting \")
		if c == '"' || c == '`' {
			b.WriteByte(c)
			quote := c
			i++
			for i < n {
				if s[i] == '\\' && quote == '"' && i+1 < n {
					b.WriteByte(s[i])
					b.WriteByte(s[i+1])
					i += 2
					continue
				}
				b.WriteByte(s[i])
				if s[i] == quote {
					i++
					break
				}
				i++
			}
			continue
		}
		b.WriteByte(c)
		i++
	}
	return b.String()
}

// Sanity check — ensures the comment-stripper actually strips and does
// not accidentally mask real code. Safety net for the invariant test.
func TestStripGoComments_StripsCommentsAndKeepsCode(t *testing.T) {
	cases := []struct {
		name string
		in   string
		want string
	}{
		{"line comment", `x := 1 // legacy note`, `x := 1 `},
		{"block comment", `x := /* bad */ 1`, `x :=  1`},
		{"string with //", `s := "http://x" // note`, `s := "http://x" `},
		{"multi-line block", "a\n/* skip\nme */b", "a\nb"},
	}
	for _, tc := range cases {
		got := stripGoComments(tc.in)
		if got != tc.want {
			t.Errorf("%s: stripGoComments(%q) = %q, want %q", tc.name, tc.in, got, tc.want)
		}
	}
}

