package records

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

	"github.com/nexuscrm/kernel/pkg/schemacatalog"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

type recordingPermissionStateAuthority struct {
	calls int
	ctx   context.Context
	err   error
}

func (authority *recordingPermissionStateAuthority) RequestPermissionSetAuthoringAdvance(context.Context, string) error {
	return authority.err
}

func (authority *recordingPermissionStateAuthority) RequestAdvanceIfChanged(
	ctx context.Context,
	changed bool,
) error {
	if !changed {
		return authority.err
	}
	authority.calls++
	authority.ctx = ctx
	return authority.err
}

func TestPermissionStateSemanticProjectionUsesManagedCatalog(t *testing.T) {
	inventory, err := schemacatalog.ManagedPermissionStateMutationInventory()
	if err != nil {
		t.Fatal(err)
	}
	for _, surface := range inventory.Surfaces() {
		t.Run(surface.SurfaceID, func(t *testing.T) {
			field := surface.SemanticFields[0]
			before := models.SObject{field: "before", "Unrelated": "old"}
			same := models.SObject{field: "before", "Unrelated": "new"}
			changed := models.SObject{field: "after", "Unrelated": "old"}

			if got, err := permissionStateSemanticProjectionChanged(surface.TableName, before, same); err != nil || got {
				t.Fatalf("non-semantic update = changed %t, error %v", got, err)
			}
			if got, err := permissionStateSemanticProjectionChanged(surface.TableName, before, changed); err != nil || !got {
				t.Fatalf("semantic update = changed %t, error %v", got, err)
			}
			if got, err := permissionStateSemanticProjectionChanged(surface.TableName, nil, changed); err != nil || !got {
				t.Fatalf("insert = changed %t, error %v", got, err)
			}
			if got, err := permissionStateSemanticProjectionChanged(surface.TableName, before, nil); err != nil || !got {
				t.Fatalf("delete = changed %t, error %v", got, err)
			}
			if got, err := permissionStateSemanticProjectionChanged(
				surface.TableName,
				models.SObject{field: "same"},
				models.SObject{lowerASCII(field): "same"},
			); err != nil || got {
				t.Fatalf("case-insensitive field lookup = changed %t, error %v", got, err)
			}
		})
	}

	if got, err := permissionStateSemanticProjectionChanged(
		"UnmanagedObject",
		models.SObject{"Id": "before"},
		models.SObject{"Id": "after"},
	); err != nil || got {
		t.Fatalf("unmanaged update = changed %t, error %v", got, err)
	}
	if got, err := permissionStateSemanticProjectionChanged(
		"User",
		models.SObject{"Id": "005-user"},
		nil,
	); err != nil || !got {
		t.Fatalf("non-surface cascade-parent delete = changed %t, error %v", got, err)
	}
}

func TestGenericPermissionStateMutationCallsAuthorityOnlyForSemanticChange(t *testing.T) {
	authority := &recordingPermissionStateAuthority{}
	service := &PersistenceService{}
	if err := service.SetPermissionStateAuthority(authority); err != nil {
		t.Fatal(err)
	}
	ctx := context.WithValue(context.Background(), struct{}{}, "transaction")
	tableName := "PermissionSetAssignment"
	surface, managed, err := managedPermissionStateMutationSurface(tableName)
	if err != nil || !managed {
		t.Fatalf("surface = %+v, managed %t, error %v", surface, managed, err)
	}
	field := surface.SemanticFields[0]
	before := models.SObject{field: "same", "Description": "before"}

	if err := service.requestPermissionStateAdvanceForMutation(
		ctx,
		tableName,
		before,
		models.SObject{field: "same", "Description": "after"},
	); err != nil {
		t.Fatal(err)
	}
	if err := service.requestPermissionStateAdvanceForMutation(
		ctx,
		"UnmanagedObject",
		models.SObject{"Id": "before"},
		models.SObject{"Id": "after"},
	); err != nil {
		t.Fatal(err)
	}
	if authority.calls != 0 {
		t.Fatalf("authority calls = %d, want zero for semantic no-ops", authority.calls)
	}

	if err := service.requestPermissionStateAdvanceForMutation(
		ctx,
		tableName,
		before,
		models.SObject{field: "changed"},
	); err != nil {
		t.Fatal(err)
	}
	if authority.calls != 1 || authority.ctx != ctx {
		t.Fatalf("authority = calls %d context %p, want one call with %p", authority.calls, authority.ctx, ctx)
	}

	advanceFailure := errors.New("advance failed")
	authority.err = advanceFailure
	if err := service.requestPermissionStateAdvanceForMutation(ctx, tableName, nil, models.SObject{field: "new"}); !errors.Is(err, advanceFailure) {
		t.Fatalf("advance error = %v, want %v", err, advanceFailure)
	}
}

func TestGenericPermissionStateSoftDeleteUsesCascadeClosure(t *testing.T) {
	authority := &recordingPermissionStateAuthority{}
	service := &PersistenceService{}
	if err := service.SetPermissionStateAuthority(authority); err != nil {
		t.Fatal(err)
	}
	before := map[string]interface{}{"Id": "005-user", "IsDeleted": 0}
	after := map[string]interface{}{"Id": "005-user", "IsDeleted": 1}
	if err := service.requestPermissionStateAdvanceForDelete(
		context.Background(),
		"User",
		before,
		after,
	); err != nil {
		t.Fatal(err)
	}
	if authority.calls != 1 {
		t.Fatalf("soft User delete authority calls = %d, want one", authority.calls)
	}
	if err := service.requestPermissionStateAdvanceForMutation(
		context.Background(),
		"User",
		before,
		after,
	); err != nil {
		t.Fatal(err)
	}
	if authority.calls != 1 {
		t.Fatalf("ordinary User update authority calls = %d, want unchanged", authority.calls)
	}
}

func TestGenericPermissionStateProtectionForcesPessimisticBoundary(t *testing.T) {
	service := &PersistenceService{}
	if !service.permissionStateRequiresPessimisticTransaction("PermissionSetAssignment") {
		t.Fatal("unwired managed surface did not force a rollback-capable transaction")
	}
	if err := service.SetPermissionStateAuthority(&recordingPermissionStateAuthority{}); err != nil {
		t.Fatal(err)
	}
	if !service.permissionStateRequiresPessimisticTransaction("PermissionSetAssignment") {
		t.Fatal("managed Permission State surface did not require a pessimistic transaction")
	}
	if service.permissionStateRequiresPessimisticTransaction("UnmanagedObject") {
		t.Fatal("unmanaged object required a Permission State pessimistic transaction")
	}
	if !service.permissionStateDeleteRequiresPessimisticTransaction("User") {
		t.Fatal("non-surface cascade-parent delete did not require a pessimistic transaction")
	}
}

func TestManagedPermissionSetDefinitionGuardForcesPessimisticBoundary(t *testing.T) {
	service := &PersistenceService{}
	if !service.permissionStateRequiresPessimisticTransaction(constants.TableConnectedAppAccess) {
		t.Fatal("Connected App access guard did not force a pessimistic transaction")
	}
}

func TestGenericPermissionStateMutationRejectsMissingAuthority(t *testing.T) {
	service := &PersistenceService{}
	if err := service.requestPermissionStateAdvanceForMutation(
		context.Background(),
		"PermissionSetAssignment",
		nil,
		models.SObject{"AssigneeId": "005-user"},
	); err == nil {
		t.Fatal("managed mutation without an authority returned nil error")
	}
	if err := service.requestPermissionStateAdvanceForDelete(
		context.Background(),
		"User",
		models.SObject{"Id": "005-user"},
		nil,
	); err == nil {
		t.Fatal("managed cascade delete without an authority returned nil error")
	}
	if err := service.requestPermissionStateAdvanceForMutation(
		context.Background(),
		"UnmanagedObject",
		models.SObject{"Id": "before"},
		models.SObject{"Id": "after"},
	); err != nil {
		t.Fatalf("unmanaged mutation without an authority: %v", err)
	}
}

func TestGenericPermissionStateOperationFamilyClosure(t *testing.T) {
	tests := []struct {
		file     string
		function string
		calls    []string
	}{
		{"persistence_insert.go", "Insert", []string{"requestPermissionStateAdvanceForRecordMutation"}},
		{"persistence_update_helpers.go", "completeUpdateAfterBeforeSaveWithPostWrite", []string{"requestPermissionStateAdvanceForMutation"}},
		{"persistence_delete.go", "deleteWithOutcomesAuthorized", []string{"requestPermissionStateAdvanceForDelete"}},
		{"persistence_bulk_insert.go", "BulkInsert", []string{"requestPermissionStateAdvanceForMutation"}},
		{"persistence_recyclebin_purge.go", "purgeRecycleBinTombstoneTreeInTransaction", []string{"requestPermissionStateAdvanceForMutation"}},
		{"persistence_raw_access.go", "PhysicalDeleteRaw", []string{"requestPermissionStateAdvanceForMutation"}},
		{"persistence_prepared_update.go", "CommitPreparedUpdate", []string{"completeUpdateAfterBeforeSave"}},
		{"persistence_recyclebin_restore.go", "completeRecycleBinRestoreUpdateLifecycle", []string{"completeUpdateAfterBeforeSave"}},
		{"persistence_bulk_ops.go", "CreateRecords", []string{"Insert"}},
		{"persistence_bulk_ops.go", "UpdateRecords", []string{"Update"}},
		{"persistence_bulk_ops.go", "DeleteRecords", []string{"Delete"}},
		{"persistence_bulk_ops.go", "UpsertRecords", []string{"Insert", "Update"}},
		{"persistence_upsert.go", "UpsertByExternalID", []string{"upsertByExternalID"}},
		{"persistence_upsert.go", "UpsertByUniqueExternalID", []string{"upsertByExternalID"}},
		{"persistence_upsert.go", "UpsertByExternalIDWithAdoptionGuards", []string{"upsertByExternalID"}},
		{"persistence_upsert.go", "upsertByExternalID", []string{"Insert", "Update"}},
		{"persistence_tree_nodes.go", "CreateTreeNodes", []string{"insertTreeNodeIR"}},
		{"persistence_tree_nodes.go", "insertTreeNodeIR", []string{"Insert"}},
	}
	root := recordsPackageRoot(t)
	for _, test := range tests {
		t.Run(test.function, func(t *testing.T) {
			calls := functionCallNames(t, filepath.Join(root, test.file), test.function)
			for _, required := range test.calls {
				if calls[required] == 0 {
					t.Errorf("%s.%s does not reach %s", test.file, test.function, required)
				}
			}
		})
	}
}

func TestProductionGenericPermissionStateCompositionIsExplicit(t *testing.T) {
	root := filepath.Dir(recordsPackageRoot(t))
	tests := []struct {
		file     string
		function string
		calls    []string
	}{
		{
			"backend/internal/application/services/infrastructure_deps_builder.go",
			"BuildInfrastructureDeps",
			[]string{"NewPermissionStateAuthority"},
		},
		{
			"backend/internal/application/services/service_manager_init.go",
			"initAutomationStack",
			[]string{"SetPermissionStateAuthority"},
		},
		{
			"backend/internal/runtimeassembly/recordmutation/records_mutation.go",
			"newRecordsMutationEngineRuntime",
			[]string{"NewPermissionStateAuthority", "SetPermissionStateAuthority"},
		},
	}
	for _, test := range tests {
		t.Run(test.function, func(t *testing.T) {
			calls := functionCallNames(t, filepath.Join(root, test.file), test.function)
			for _, required := range test.calls {
				if calls[required] == 0 {
					t.Errorf("%s.%s does not call %s", test.file, test.function, required)
				}
			}
		})
	}
}

func functionCallNames(t *testing.T, path, functionName string) map[string]int {
	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 {
			continue
		}
		calls := make(map[string]int)
		ast.Inspect(function.Body, func(node ast.Node) bool {
			call, ok := node.(*ast.CallExpr)
			if !ok {
				return true
			}
			switch target := call.Fun.(type) {
			case *ast.Ident:
				calls[target.Name]++
			case *ast.SelectorExpr:
				calls[target.Sel.Name]++
			}
			return true
		})
		return calls
	}
	t.Fatalf("function %s not found in %s", functionName, path)
	return nil
}

func recordsPackageRoot(t *testing.T) string {
	t.Helper()
	_, current, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("resolve records package root")
	}
	return filepath.Dir(current)
}

func lowerASCII(value string) string {
	bytes := []byte(value)
	for index, char := range bytes {
		if char >= 'A' && char <= 'Z' {
			bytes[index] = char + ('a' - 'A')
		}
	}
	return string(bytes)
}
