package controlplane

import (
	"go/ast"
	"go/parser"
	"go/token"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"testing"
)

func TestVerifyL0DoesNotCallRunInsertRulesValidators(t *testing.T) {
	verifyL0 := parseControlplaneFuncDecl(t, "verify_l0.go", "VerifyL0")
	if call := findFuncCallByName(verifyL0, "runInsertRulesValidators"); call != nil {
		t.Fatalf("VerifyL0 must not call runInsertRulesValidators; found call at %s", controlplaneCallPos(t, "verify_l0.go", call))
	}
}

func TestRunAssertionsDoesNotCallCreateFromSourceGuardCoverage(t *testing.T) {
	assertionsPath := filepath.Join("bootstrapimpl", "assertions.go")
	runAssertions := parseControlplaneFuncDecl(t, assertionsPath, "RunAssertions")
	if call := findFuncCallByName(runAssertions, "assertCreateFromSourceGuardCoverage"); call != nil {
		t.Fatalf("RunAssertions must stay L0-only; found create-from-source guard call at %s", controlplaneCallPos(t, assertionsPath, call))
	}
}

func TestVerifyDoesNotCallCreateFromSourceGuardCoverage(t *testing.T) {
	verify := parseControlplaneFuncDecl(t, "verify.go", "Verify")
	if call := findFuncCallByName(verify, "VerifyCreateFromSourceGuardCoverage"); call != nil {
		t.Fatalf("Verify must stay tenant-agnostic; found create-from-source guard call at %s", controlplaneCallPos(t, "verify.go", call))
	}
}

func TestCheckObjectOwnershipPayloadFlagsCoversColumnScopedCustomizedBits(t *testing.T) {
	fn := parseControlplaneFuncDecl(t, "verify_l0.go", "checkObjectOwnershipPayloadFlags")
	literals := stringLiteralsInFunc(fn)
	for _, bit := range []string{"ListFieldsCustomized", "DefaultChildRowsCustomized", "ChildCloneRulesCustomized"} {
		if !containsCustomizedBitPredicate(literals, bit) {
			t.Fatalf("checkObjectOwnershipPayloadFlags missing customized-bit predicate for %s", bit)
		}
	}
}

func parseControlplaneFuncDecl(t *testing.T, filename, funcName string) *ast.FuncDecl {
	t.Helper()

	_, here, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("runtime.Caller failed")
	}
	path := filepath.Join(filepath.Dir(here), filename)
	fset := token.NewFileSet()
	file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
	if err != nil {
		t.Fatalf("parse %s: %v", path, err)
	}
	for _, decl := range file.Decls {
		fn, ok := decl.(*ast.FuncDecl)
		if ok && fn.Name != nil && fn.Name.Name == funcName {
			return fn
		}
	}
	t.Fatalf("function %s not found in %s", funcName, path)
	return nil
}

func findFuncCallByName(fn *ast.FuncDecl, want string) *ast.CallExpr {
	var found *ast.CallExpr
	ast.Inspect(fn.Body, func(n ast.Node) bool {
		if found != nil {
			return false
		}
		call, ok := n.(*ast.CallExpr)
		if !ok {
			return true
		}
		switch fun := call.Fun.(type) {
		case *ast.Ident:
			if fun.Name == want {
				found = call
				return false
			}
		case *ast.SelectorExpr:
			if fun.Sel != nil && fun.Sel.Name == want {
				found = call
				return false
			}
		}
		return true
	})
	return found
}

func controlplaneCallPos(t *testing.T, filename string, call *ast.CallExpr) string {
	t.Helper()

	_, here, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("runtime.Caller failed")
	}
	path := filepath.Join(filepath.Dir(here), filename)
	fset := token.NewFileSet()
	file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
	if err != nil {
		t.Fatalf("parse %s: %v", path, err)
	}
	_ = file
	return fset.Position(call.Pos()).String()
}

func stringLiteralsInFunc(fn *ast.FuncDecl) []string {
	var out []string
	ast.Inspect(fn.Body, func(n ast.Node) bool {
		lit, ok := n.(*ast.BasicLit)
		if !ok || lit.Kind != token.STRING {
			return true
		}
		value, err := strconv.Unquote(lit.Value)
		if err == nil {
			out = append(out, value)
		}
		return true
	})
	return out
}

func containsCustomizedBitPredicate(literals []string, bit string) bool {
	patterns := []*regexp.Regexp{
		regexp.MustCompile(`\b` + regexp.QuoteMeta(bit) + `\b.*<> 0`),
		regexp.MustCompile(`COALESCE\(` + regexp.QuoteMeta(bit) + `,\s*0\)\s*<>\s*0`),
	}
	for _, literal := range literals {
		for _, pattern := range patterns {
			if pattern.MatchString(strings.ReplaceAll(literal, "`", "")) {
				return true
			}
		}
	}
	return false
}
