package persistence

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

	"github.com/nexuscrm/kernel/pkg/schemacatalog"
)

var permissionRepositoryDirectWriterClosure = []string{
	"nexuscrm-persistence/application_navigation.go#ApplicationNavigationRepository.Initialize",
	"nexuscrm-persistence/application_navigation.go#ApplicationNavigationRepository.Store",
	"nexuscrm-persistence/metadata_record_types.go#MetadataRepository.CreateOrReviveRecordType",
	"nexuscrm-persistence/metadata_record_types.go#MetadataRepository.DeleteRecordType",
	"nexuscrm-persistence/metadata_record_types.go#MetadataRepository.UpdateRecordType",
	"nexuscrm-persistence/perm_app_tab.go#PermissionRepository.ensureProfileAppVisibilityRows",
	"nexuscrm-persistence/perm_custom.go#PermissionRepository.CreateCustomPermission",
	"nexuscrm-persistence/perm_custom.go#PermissionRepository.DeleteCustomPermission",
	"nexuscrm-persistence/perm_custom.go#PermissionRepository.UpdateCustomPermission",
	"nexuscrm-persistence/perm_groups.go#PermissionRepository.CreatePermissionSetGroup",
	"nexuscrm-persistence/perm_groups.go#PermissionRepository.DeletePermissionSetGroup",
	"nexuscrm-persistence/perm_groups.go#PermissionRepository.UpdatePermissionSetGroup",
	"nexuscrm-persistence/perm_object_field.go#PermissionRepository.DeleteFieldPermissionsForFields",
	"nexuscrm-persistence/perm_object_field.go#PermissionRepository.InsertMissingFieldPermissions",
	"nexuscrm-persistence/perm_object_field.go#PermissionRepository.InsertMissingObjectPermissions",
	"nexuscrm-persistence/perm_object_field.go#PermissionRepository.upsertFieldPermission",
	"nexuscrm-persistence/perm_object_field.go#PermissionRepository.upsertObjectPermission",
	"nexuscrm-persistence/perm_recordtype_custom.go#PermissionRepository.DeletePermissionSetCustomPermGrant",
	"nexuscrm-persistence/perm_recordtype_custom.go#PermissionRepository.SetPermissionSetCustomPermGrant",
	"nexuscrm-persistence/perm_recordtype_custom.go#PermissionRepository.SetPermissionSetRecordTypeGrant",
	"nexuscrm-persistence/perm_recordtype_custom.go#PermissionRepository.deletePermissionSetRecordTypeGrant",
	"nexuscrm-persistence/perm_recordtype_custom.go#PermissionRepository.deleteProfileCustomPermGrant",
	"nexuscrm-persistence/perm_recordtype_custom.go#PermissionRepository.setProfileCustomPermGrant",
	"nexuscrm-persistence/perm_sets.go#PermissionRepository.BulkEnsureProfilePermissionSets",
	"nexuscrm-persistence/perm_sets.go#PermissionRepository.CleanupProfilePermissionSet",
	"nexuscrm-persistence/perm_sets.go#PermissionRepository.CreatePermissionSet",
	"nexuscrm-persistence/perm_sets.go#PermissionRepository.DeletePermissionSet",
	"nexuscrm-persistence/perm_sets.go#PermissionRepository.EnsureProfilePermissionSet",
	"nexuscrm-persistence/perm_sets.go#PermissionRepository.UpdatePermissionSet",
	"nexuscrm-persistence/perm_sets.go#PermissionRepository.bulkReactivatePermissionSets",
	"nexuscrm-persistence/perm_system.go#PermissionRepository.AdvancePermissionSetAuthoringRevision",
	"nexuscrm-persistence/perm_system.go#PermissionRepository.SetSystemPermission",
	"nexuscrm-persistence/perm_system.go#PermissionRepository.UpsertSystemPermission",
	"nexuscrm-persistence/repo_app.go#MetadataRepository.DeleteApp",
	"nexuscrm-persistence/repo_app.go#MetadataRepository.createAppWithExec",
	"nexuscrm-persistence/repo_app.go#MetadataRepository.ensureCanonicalAppVisibilityRows",
	"nexuscrm-persistence/repo_app.go#MetadataRepository.updateAppWithExec",
	"nexuscrm-persistence/repo_layout.go#MetadataRepository.SetLayoutAuthoringListFields",
	"nexuscrm-persistence/schema_ddl_ops.go#SchemaRepository.dropTableMetadataInOwnedPessimisticTransaction",
	"nexuscrm-persistence/schema_metadata_persist.go#SchemaRepository.InsertObjectMetadata",
	"nexuscrm-persistence/schema_metadata_persist.go#SchemaRepository.SaveObjectMetadata",
	"nexuscrm-persistence/schema_metadata_persist.go#SchemaRepository.batchSaveObjectMetadata",
	"nexuscrm-persistence/user_repository.go#UserRepository.DeleteUser",
}

func TestPermissionRepositoryDirectWriterClosureUsesGeneratedWriterIDs(t *testing.T) {
	root := permissionStateDirectWriterRepositoryRoot(t)
	for _, writerID := range permissionRepositoryDirectWriterClosure {
		t.Run(writerID, func(t *testing.T) {
			sourcePath, symbol, ok := strings.Cut(writerID, "#")
			if !ok {
				t.Fatalf("invalid test writer ID %q", writerID)
			}
			_, functionName, ok := strings.Cut(symbol, ".")
			if !ok {
				t.Fatalf("invalid test writer symbol %q", symbol)
			}
			function := parsePermissionStateDirectWriterFunction(
				t,
				filepath.Join(root, sourcePath),
				functionName,
			)
			calls := make(map[string]int)
			hasWriterID := false
			ast.Inspect(function.Body, func(node ast.Node) bool {
				switch value := node.(type) {
				case *ast.BasicLit:
					if value.Kind == token.STRING {
						decoded, err := strconv.Unquote(value.Value)
						if err == nil && decoded == writerID {
							hasWriterID = true
						}
					}
				case *ast.CallExpr:
					if selector, ok := value.Fun.(*ast.SelectorExpr); ok {
						calls[selector.Sel.Name]++
					}
				}
				return true
			})
			runCalls := calls["runPermissionStateDirectWriter"] + calls["runProjected"]
			if !hasWriterID ||
				calls["permissionStateDirectWriterShouldRun"] != 1 ||
				runCalls != 1 {
				t.Fatalf(
					"writer closure id=%t shouldRun=%d run=%d",
					hasWriterID,
					calls["permissionStateDirectWriterShouldRun"],
					runCalls,
				)
			}
		})
	}
}

func TestPermissionRepositoryDirectWriterClosureMatchesGeneratedInventory(t *testing.T) {
	inventory, err := schemacatalog.ManagedPermissionStateDirectWriterInventory()
	if err != nil {
		t.Fatal(err)
	}
	var generated []string
	for _, writer := range inventory.Writers() {
		if strings.Contains(writer.WriterID, "#ApplicationNavigationRepository.") || strings.Contains(writer.WriterID, "#PermissionRepository.") ||
			strings.Contains(writer.WriterID, "#MetadataRepository.") ||
			strings.Contains(writer.WriterID, "#SchemaRepository.") ||
			strings.Contains(writer.WriterID, "#UserRepository.") {
			generated = append(generated, writer.WriterID)
		}
	}
	if !reflect.DeepEqual(generated, permissionRepositoryDirectWriterClosure) {
		t.Fatalf(
			"PermissionRepository direct writer closure =\n%q\nwant generated inventory =\n%q",
			permissionRepositoryDirectWriterClosure,
			generated,
		)
	}
}

func TestPermissionRepositoryNavigationDefaultsOwnOneDirectWriterTransaction(t *testing.T) {
	root := permissionStateDirectWriterRepositoryRoot(t)
	function := parsePermissionStateDirectWriterFunction(
		t,
		filepath.Join(root, "nexuscrm-persistence/perm_app_tab.go"),
		"EnsureNavigationVisibilityForProfile",
	)
	calls := make(map[string]int)
	ast.Inspect(function.Body, func(node ast.Node) bool {
		call, ok := node.(*ast.CallExpr)
		if !ok {
			return true
		}
		if selector, ok := call.Fun.(*ast.SelectorExpr); ok {
			calls[selector.Sel.Name]++
		}
		return true
	})
	for _, required := range []string{
		"runPermissionStateDirectWriterGroup",
		"ensureProfileAppVisibilityRows",
	} {
		if calls[required] == 0 {
			t.Errorf("EnsureNavigationVisibilityForProfile does not call %s", required)
		}
	}
	if calls["ensureProfileTabSettingRows"] != 0 {
		t.Error("profile creation must not manufacture tab grants from navigation")
	}
}

func TestProductionPermissionRepositoryDirectWriterCompositionIsExplicit(t *testing.T) {
	root := permissionStateDirectWriterRepositoryRoot(t)
	tests := []struct {
		path     string
		function string
		calls    []string
	}{
		{
			path:     "backend/internal/application/services/infrastructure_deps_builder.go",
			function: "BuildInfrastructureDeps",
			calls: []string{
				"NewPermissionStateDirectWriterCoordinator",
				"SetPermissionStateDirectWriterCoordinator",
				"SetPermissionStateDirectWriterCoordinator",
				"SetPermissionStateDirectWriterCoordinator",
				"SetPermissionStateDirectWriterCoordinator",
			},
		},
		{
			path:     "backend/internal/runtimeassembly/recordmutation/records_mutation.go",
			function: "newRecordsMutationEngineRuntime",
			calls: []string{
				"NewPermissionStateDirectWriterCoordinator",
				"SetPermissionStateDirectWriterCoordinator",
				"SetPermissionStateDirectWriterCoordinator",
			},
		},
	}
	for _, test := range tests {
		t.Run(test.function, func(t *testing.T) {
			function := parsePermissionStateDirectWriterFunction(
				t,
				filepath.Join(root, test.path),
				test.function,
			)
			calls := make(map[string]int)
			ast.Inspect(function.Body, func(node ast.Node) bool {
				call, ok := node.(*ast.CallExpr)
				if !ok {
					return true
				}
				if selector, ok := call.Fun.(*ast.SelectorExpr); ok {
					calls[selector.Sel.Name]++
				}
				return true
			})
			requiredCounts := make(map[string]int)
			for _, required := range test.calls {
				requiredCounts[required]++
			}
			for required, want := range requiredCounts {
				if calls[required] != want {
					t.Errorf("%s calls %s %d times, want %d", test.function, required, calls[required], want)
				}
			}
		})
	}
}

func parsePermissionStateDirectWriterFunction(
	t *testing.T,
	path string,
	functionName string,
) *ast.FuncDecl {
	t.Helper()
	file, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
	if err != nil {
		t.Fatal(err)
	}
	for _, declaration := range file.Decls {
		function, ok := declaration.(*ast.FuncDecl)
		if ok && function.Name.Name == functionName && function.Body != nil {
			return function
		}
	}
	t.Fatalf("function %s not found in %s", functionName, path)
	return nil
}

func permissionStateDirectWriterRepositoryRoot(t *testing.T) string {
	t.Helper()
	_, current, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("resolve permission state direct writer repository root")
	}
	return filepath.Dir(filepath.Dir(current))
}
