package packs

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

func TestVerifyPackObjectDefaultChildRowsConsultsResolverBeforeLiveValidation(t *testing.T) {
	fn := parsePacksFuncDecl(t, "pack_schema_verify.go", "VerifyPackObjectDefaultChildRows")
	verifyLivePos := token.NoPos
	resolverPos := token.NoPos

	ast.Inspect(fn.Body, func(n ast.Node) bool {
		call, ok := n.(*ast.CallExpr)
		if !ok {
			return true
		}
		switch fun := call.Fun.(type) {
		case *ast.Ident:
			if fun.Name == "verifyLivePackDefaultChildRows" && verifyLivePos == token.NoPos {
				verifyLivePos = call.Pos()
			}
			if fun.Name == "ResolveDefaultChildRowsValueOwnership" && resolverPos == token.NoPos {
				resolverPos = call.Pos()
			}
		}
		return true
	})

	if verifyLivePos == token.NoPos {
		t.Fatal("VerifyPackObjectDefaultChildRows is missing verifyLivePackDefaultChildRows call")
	}
	if resolverPos == token.NoPos {
		t.Fatal("VerifyPackObjectDefaultChildRows must consult ResolveDefaultChildRowsValueOwnership before live validation")
	}
	if resolverPos > verifyLivePos {
		t.Fatalf("ResolveDefaultChildRowsValueOwnership appears after verifyLivePackDefaultChildRows in VerifyPackObjectDefaultChildRows")
	}
}

func TestVerifyLivePackDefaultChildRowsHasSingleProductionCallSite(t *testing.T) {
	files := parseProductionPacksFiles(t)
	var callCount int
	var callerNames []string
	for _, file := range files {
		for _, decl := range file.Decls {
			fn, ok := decl.(*ast.FuncDecl)
			if !ok || fn.Body == nil {
				continue
			}
			ast.Inspect(fn.Body, func(n ast.Node) bool {
				call, ok := n.(*ast.CallExpr)
				if !ok {
					return true
				}
				if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "verifyLivePackDefaultChildRows" {
					callCount++
					callerNames = append(callerNames, fn.Name.Name)
				}
				return true
			})
		}
	}

	if callCount != 1 {
		t.Fatalf("verifyLivePackDefaultChildRows production call count = %d, want 1 (callers=%v)", callCount, callerNames)
	}
	if callerNames[0] != "VerifyPackObjectDefaultChildRows" {
		t.Fatalf("verifyLivePackDefaultChildRows caller = %s, want VerifyPackObjectDefaultChildRows", callerNames[0])
	}
}

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

	path := filepath.Join(packsDir(t), 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 parseProductionPacksFiles(t *testing.T) []*ast.File {
	t.Helper()

	paths, err := filepath.Glob(filepath.Join(packsDir(t), "*.go"))
	if err != nil {
		t.Fatalf("glob packs files: %v", err)
	}
	fset := token.NewFileSet()
	files := make([]*ast.File, 0, len(paths))
	for _, path := range paths {
		if filepath.Ext(path) != ".go" || filepath.Base(path) == "" || filepath.Base(path) == "" {
			continue
		}
		if len(path) >= len("_test.go") && path[len(path)-len("_test.go"):] == "_test.go" {
			continue
		}
		file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
		if err != nil {
			t.Fatalf("parse %s: %v", path, err)
		}
		files = append(files, file)
	}
	return files
}

func packsDir(t *testing.T) string {
	t.Helper()

	_, here, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("runtime.Caller failed")
	}
	return filepath.Dir(here)
}
