package boundary_test

import (
	"go/scanner"
	"go/token"
	"path/filepath"
	"strings"
	"testing"
)

// L0-D D6 boundary seal: row-visibility enforcement must branch only on the
// neutral models.RowVisibilityFor translation, never on the Salesforce OWD
// constants.SharingModel* tokens directly.
//
// The SharingModel taxonomy is a Salesforce-derived vocabulary. shared/pkg/
// models/row_visibility.go is the ONE app-blind translator that maps it into a
// neutral RowVisibility policy; every row-visibility enforcement site must read
// that policy instead of switching on the raw tokens. This seal freezes that
// invariant for the four enforcement files so a future change cannot re-scatter
// SharingModel branching back across app-blind L0.
//
// It is the row-visibility COUNTERPART to the taxonomy owner-freeze
// TestSharingModelTaxonomyStaysLedgered (which pins the enum source file); this
// seal must NOT touch or flip that ledger.

// rowVisibilityEnforcementFiles are the four row-visibility enforcement sites
// that must route through models.RowVisibilityFor and must not branch on
// constants.SharingModel* in executable code.
var rowVisibilityEnforcementFiles = []string{
	"nexuscrm-permissions/rls_filter.go",
	"nexuscrm-permissions/record_access.go",
	"nexuscrm-analytics/query_clause_fls.go",
	"nexuscrm-metadata/metadata_sharing.go",
}

// TestSharingModelRowVisibilityBranchingConfined asserts that each row-visibility
// enforcement file contains 0 executable (non-comment) references to a
// constants.SharingModel* selector and positively reads the neutral translator
// models.RowVisibilityFor(.
//
// ALLOWLIST — the following are intentionally NOT in the enforcement set and
// legitimately reference the SharingModel taxonomy, so they must not be scanned
// here:
//   - shared/pkg/models/row_visibility.go — the ONE app-blind translator home;
//     it owns the SharingModel switch.
//   - nexuscrm-permissions/service.go, service_effective_permissions.go,
//     parent_controlled.go — these read SharingModelControlledByParent for
//     parent-relationship routing (which object owns the parent share), NOT for
//     row visibility.
func TestSharingModelRowVisibilityBranchingConfined(t *testing.T) {
	root := repoRoot(t)
	for _, rel := range rowVisibilityEnforcementFiles {
		path := filepath.Join(root, filepath.FromSlash(rel))
		src := readFile(t, path)
		if n := countConstantsSharingModelSelectors(t, rel, src); n > 0 {
			t.Errorf("%s branches on a constants.SharingModel* selector in executable code (%d occurrence(s)); row-visibility enforcement must read models.RowVisibilityFor instead", rel, n)
		}
		if !strings.Contains(src, "models.RowVisibilityFor(") {
			t.Errorf("%s does not call models.RowVisibilityFor(; row-visibility enforcement must route through the neutral translator", rel)
		}
	}
}

// TestSharingModelSelectorMatcherIgnoresComments documents the matcher's
// contract: a constants.SharingModel* mention inside a comment must not count
// (every enforcement file has such comments), while a real selector in code
// must. This guards the seal against both false positives and false negatives.
func TestSharingModelSelectorMatcherIgnoresComments(t *testing.T) {
	commentOnly := "package sample\n\n// branch on constants.SharingModelPrivate here\nfunc f() {}\n"
	if n := countConstantsSharingModelSelectors(t, "comment.go", commentOnly); n != 0 {
		t.Fatalf("comment-only fixture counted %d constants.SharingModel selectors, want 0", n)
	}

	codeToken := "package sample\n\nvar x = constants.SharingModelPrivate\nvar y = constants.SharingModelControlledByParent\n"
	if n := countConstantsSharingModelSelectors(t, "code.go", codeToken); n != 2 {
		t.Fatalf("code fixture counted %d constants.SharingModel selectors, want 2", n)
	}

	// A SharingModel identifier that is NOT qualified by the constants package
	// (e.g. a struct field access schema.SharingModel) is not a taxonomy-token
	// selector and must not be counted.
	fieldAccess := "package sample\n\nvar z = schema.SharingModel\n"
	if n := countConstantsSharingModelSelectors(t, "field.go", fieldAccess); n != 0 {
		t.Fatalf("field-access fixture counted %d selectors, want 0 (schema.SharingModel is a field, not a constants token)", n)
	}
}

// countConstantsSharingModelSelectors tokenizes src (comments discarded) and
// counts occurrences of the executable selector pattern
// `constants` `.` `SharingModel*`, i.e. a reference to a Salesforce OWD taxonomy
// constant. go/scanner in mode 0 skips comments, so comment mentions never
// count.
func countConstantsSharingModelSelectors(t *testing.T, name, src string) int {
	t.Helper()
	fset := token.NewFileSet()
	file := fset.AddFile(name, fset.Base(), len(src))
	var s scanner.Scanner
	s.Init(file, []byte(src), func(_ token.Position, msg string) {
		t.Fatalf("scan %s: %s", name, msg)
	}, 0)

	type tokLit struct {
		tok token.Token
		lit string
	}
	var toks []tokLit
	for {
		_, tok, lit := s.Scan()
		if tok == token.EOF {
			break
		}
		toks = append(toks, tokLit{tok: tok, lit: lit})
	}

	count := 0
	for i := 0; i+2 < len(toks); i++ {
		if toks[i].tok == token.IDENT && toks[i].lit == "constants" &&
			toks[i+1].tok == token.PERIOD &&
			toks[i+2].tok == token.IDENT && strings.HasPrefix(toks[i+2].lit, "SharingModel") {
			count++
		}
	}
	return count
}
