package schemacatalog

import (
	"bytes"
	"strings"
	"testing"

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

func TestManagedPermissionStateMutationInventoryDeclaresReviewedSurfaces(t *testing.T) {
	inventory, err := ManagedPermissionStateMutationInventory()
	if err != nil {
		t.Fatal(err)
	}
	surfaces := inventory.Surfaces()
	if len(surfaces) != 23 {
		t.Fatalf("surfaces = %+v, want twenty-three frozen permission surfaces", surfaces)
	}
	if inventory.SchemaVersion() != PermissionStateMutationInventorySchema {
		t.Fatalf("schema = %q", inventory.SchemaVersion())
	}
	expected := map[string]struct {
		surfaceID string
		fields    string
	}{
		"_System_Permission": {
			surfaceID: "platform.permission-state.permission-catalog.v1",
			fields:    "ApiName,IsActive,RequiresPermission",
		},
		"PermissionSet": {
			surfaceID: "platform.permission-state.permission-set.v1",
			fields:    "IsDeleted,IsOwnedByProfile,ProfileId",
		},
		"_System_PermissionSetPermission": {
			surfaceID: "platform.permission-state.permission-set-system-grant.v1",
			fields:    "IsEnabled,PermissionApiName,PermissionSetId",
		},
		"PermissionSetAssignment": {
			surfaceID: "platform.permission-state.permission-set-assignment.v1",
			fields:    "AssigneeId,IsDeleted,PermissionSetId",
		},
		"_System_CustomPermission": {
			surfaceID: "platform.permission-state.custom-permission-catalog.v1",
			fields:    "ApiName,IsActive,IsDeleted",
		},
		"ProfileCustomPermission": {
			surfaceID: "platform.permission-state.profile-custom-permission.v1",
			fields:    "CustomPermissionId,ProfileId",
		},
		"PermissionSetCustomPermission": {
			surfaceID: "platform.permission-state.permission-set-custom-permission.v1",
			fields:    "CustomPermissionId,PermissionSetId",
		},
		"ObjectPermissions": {
			surfaceID: "platform.permission-state.object-permission.v1",
			fields:    "IsDeleted,ParentId,PermissionsCreate,PermissionsDelete,PermissionsEdit,PermissionsModifyAllRecords,PermissionsRead,PermissionsViewAllRecords,SobjectType",
		},
		"FieldPermissions": {
			surfaceID: "platform.permission-state.field-permission.v1",
			fields:    "Field,IsDeleted,ParentId,PermissionsEdit,PermissionsRead,SobjectType",
		},
		"PermissionSetGroup": {
			surfaceID: "platform.permission-state.permission-set-group.v1",
			fields:    "IsActive,IsDeleted",
		},
		"PermissionSetGroupComponent": {
			surfaceID: "platform.permission-state.permission-set-group-component.v1",
			fields:    "GroupId,PermissionSetId",
		},
		"PermissionSetGroupAssignment": {
			surfaceID: "platform.permission-state.permission-set-group-assignment.v1",
			fields:    "AssigneeId,GroupId,IsDeleted",
		},
		"MutingPermissionSet": {
			surfaceID: "platform.permission-state.muting-permission-set.v1",
			fields:    "GroupId",
		},
		"_System_MutingPermissionSetPermission": {
			surfaceID: "platform.permission-state.muting-system-permission.v1",
			fields:    "MutingPermissionSetId,PermissionApiName",
		},
		"_System_Object": {
			surfaceID: "platform.permission-state.object-catalog.v1",
			fields:    "ApiName,IsDeleted",
		},
		"_System_RecordType": {
			surfaceID: "platform.permission-state.record-type-catalog.v1",
			fields:    "IsActive,IsDeleted,ObjectId,SobjectType",
		},
		"PermissionSetRecordTypeVisibility": {
			surfaceID: "platform.permission-state.record-type-visibility.v1",
			fields:    "IsVisible,PermissionSetId,RecordTypeId",
		},
		"_System_App": {
			surfaceID: "platform.permission-state.app-navigation-catalog.v1",
			fields:    "FrontendAppId",
		},
		"ProfileTabSetting": {
			surfaceID: "platform.permission-state.profile-tab-setting.v1",
			fields:    "ProfileId,TabName,Visibility",
		},
		"PermissionSetTabSetting": {
			surfaceID: "platform.permission-state.permission-set-tab-setting.v1",
			fields:    "PermissionSetId,TabName,Visibility",
		},
		"ProfileAppVisibility": {
			surfaceID: "platform.permission-state.profile-app-visibility.v1",
			fields:    "AppId,IsDefault,IsVisible,ProfileId",
		},
		"PermissionSetAppVisibility": {
			surfaceID: "platform.permission-state.permission-set-app-visibility.v1",
			fields:    "AppId,IsVisible,PermissionSetId",
		},
		"_System_ConnectedAppAccess": {
			surfaceID: "platform.permission-state.connected-app-access.v1",
			fields:    "ConnectedAppId,PermissionSetId,ProfileId",
		},
	}
	for tableName, want := range expected {
		surface, ok := inventory.SurfaceForTable(tableName)
		if !ok ||
			surface.SurfaceID != want.surfaceID ||
			strings.Join(surface.SemanticFields, ",") != want.fields {
			t.Errorf("%s surface = %+v, found %t, want ID %s fields %s", tableName, surface, ok, want.surfaceID, want.fields)
		}
	}
	if got := inventory.Digest(); got != "sha256:f17237becf56a0d61e3cb12af6157c0bc2401f5d91b6fbb1b14201b5746ecd14" {
		t.Fatalf("digest = %q; freeze the reviewed canonical inventory digest", got)
	}
}

func TestPermissionStateMutationInventoryIsImmutableAndCaseInsensitiveAtLookup(t *testing.T) {
	inventory, err := ManagedPermissionStateMutationInventory()
	if err != nil {
		t.Fatal(err)
	}
	first := inventory.Surfaces()
	first[0].TableName = "changed"
	first[0].SemanticFields[0] = "changed"
	canonical := inventory.CanonicalJSON()
	canonical[0] = '!'

	second, ok := inventory.SurfaceForTable("permissionsetassignment")
	if !ok {
		t.Fatal("case-insensitive table lookup did not find the managed surface")
	}
	if second.TableName != "PermissionSetAssignment" ||
		strings.Join(second.SemanticFields, ",") != "AssigneeId,IsDeleted,PermissionSetId" ||
		!bytes.HasPrefix(inventory.CanonicalJSON(), []byte(`{"schemaVersion"`)) {
		t.Fatalf("caller mutation escaped inventory copy: surface=%+v canonical=%s", second, inventory.CanonicalJSON())
	}
	second.SemanticFields[0] = "changed again"
	third, _ := inventory.SurfaceForTable("PermissionSetAssignment")
	if third.SemanticFields[0] != "AssigneeId" {
		t.Fatalf("lookup returned mutable inventory storage: %+v", third)
	}
}

func TestPermissionStateMutationInventoryDerivesDeleteCascadeClosure(t *testing.T) {
	inventory, err := ManagedPermissionStateMutationInventory()
	if err != nil {
		t.Fatal(err)
	}
	userSurfaces := inventory.SurfacesAffectedByDelete("user")
	if len(userSurfaces) != 2 ||
		userSurfaces[0].SurfaceID != "platform.permission-state.permission-set-assignment.v1" ||
		userSurfaces[1].SurfaceID != "platform.permission-state.permission-set-group-assignment.v1" {
		t.Fatalf("User delete surfaces = %+v", userSurfaces)
	}
	userSurfaces[0].SurfaceID = "changed"
	userSurfaces[0].SemanticFields[0] = "changed"
	again := inventory.SurfacesAffectedByDelete("User")
	if again[0].SurfaceID != "platform.permission-state.permission-set-assignment.v1" ||
		again[0].SemanticFields[0] != "AssigneeId" {
		t.Fatalf("caller mutation escaped delete closure copy: %+v", again)
	}

	appSurfaces := inventory.SurfacesAffectedByDelete("_System_App")
	got := make([]string, len(appSurfaces))
	for index, surface := range appSurfaces {
		got[index] = surface.SurfaceID
	}
	if strings.Join(got, ",") != "platform.permission-state.app-navigation-catalog.v1,"+
		"platform.permission-state.permission-set-app-visibility.v1,"+
		"platform.permission-state.profile-app-visibility.v1" {
		t.Fatalf("App delete surfaces = %v", got)
	}
	if got := inventory.SurfacesAffectedByDelete("UnmanagedObject"); len(got) != 0 {
		t.Fatalf("unmanaged delete surfaces = %+v, want empty", got)
	}
}

func TestPermissionStateMutationInventoryRejectsInvalidDeclarations(t *testing.T) {
	definitions := []schema.TableDefinition{
		{
			TableName: "_System_Example",
			Columns: []schema.ColumnDefinition{
				{Name: "Id"},
				{Name: "Meaning"},
				{Name: "CreatedDate"},
				{Name: "LastModifiedDate"},
			},
		},
		{
			TableName: "_System_Other",
			Columns:   []schema.ColumnDefinition{{Name: "Id"}, {Name: "Meaning"}},
		},
		{
			TableName: "_System_PermissionStateVersion",
			Columns:   []schema.ColumnDefinition{{Name: "Revision"}},
		},
	}
	tests := []struct {
		name    string
		raw     string
		wantErr string
	}{
		{
			name:    "invalid JSON",
			raw:     `[`,
			wantErr: "parse managed catalog",
		},
		{
			name:    "empty inventory",
			raw:     `[]`,
			wantErr: "declares no semantic surfaces",
		},
		{
			name: "unknown table",
			raw: `[{"tableName":"Missing","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.example.v1","semanticFields":["Id"]}}]`,
			wantErr: "not an exact managed table",
		},
		{
			name: "wrong table case",
			raw: `[{"tableName":"_system_example","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.example.v1","semanticFields":["Id"]}}]`,
			wantErr: "not an exact managed table",
		},
		{
			name: "table whitespace",
			raw: `[{"tableName":" _System_Example ","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.example.v1","semanticFields":["Id"]}}]`,
			wantErr: "not an exact managed table",
		},
		{
			name: "invalid surface ID",
			raw: `[{"tableName":"_System_Example","permissionStateMutation":` +
				`{"surfaceId":"permission-state.example","semanticFields":["Id"]}}]`,
			wantErr: "invalid surface ID",
		},
		{
			name: "duplicate surface ID",
			raw: `[{"tableName":"_System_Example","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.example.v1","semanticFields":["Id"]}},` +
				`{"tableName":"_System_Other","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.example.v1","semanticFields":["Id"]}}]`,
			wantErr: "surface ID",
		},
		{
			name: "empty fields",
			raw: `[{"tableName":"_System_Example","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.example.v1","semanticFields":[]}}]`,
			wantErr: "semantic fields are empty",
		},
		{
			name: "unknown field",
			raw: `[{"tableName":"_System_Example","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.example.v1","semanticFields":["Missing"]}}]`,
			wantErr: "not an exact managed column",
		},
		{
			name: "wrong field case",
			raw: `[{"tableName":"_System_Example","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.example.v1","semanticFields":["meaning"]}}]`,
			wantErr: "not an exact managed column",
		},
		{
			name: "field whitespace",
			raw: `[{"tableName":"_System_Example","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.example.v1","semanticFields":[" Meaning "]}}]`,
			wantErr: "not an exact managed column",
		},
		{
			name: "duplicate field",
			raw: `[{"tableName":"_System_Example","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.example.v1","semanticFields":["Meaning","Meaning"]}}]`,
			wantErr: "duplicated",
		},
		{
			name: "audit field",
			raw: `[{"tableName":"_System_Example","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.example.v1","semanticFields":["CreatedDate"]}}]`,
			wantErr: "cannot define permission semantics",
		},
		{
			name: "protocol singleton",
			raw: `[{"tableName":"_System_PermissionStateVersion","permissionStateMutation":` +
				`{"surfaceId":"platform.permission-state.protocol.v1","semanticFields":["Revision"]}}]`,
			wantErr: "protocol singleton",
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			_, err := permissionStateMutationInventoryFromCatalog([]byte(test.raw), definitions)
			if err == nil || !strings.Contains(err.Error(), test.wantErr) {
				t.Fatalf("error = %v, want containing %q", err, test.wantErr)
			}
		})
	}
}

func TestPermissionStateMutationInventoryCanonicalizesOrder(t *testing.T) {
	definitions := []schema.TableDefinition{
		{TableName: "B", Columns: []schema.ColumnDefinition{{Name: "Z"}, {Name: "A"}}},
		{TableName: "A", Columns: []schema.ColumnDefinition{{Name: "B"}}},
	}
	raw := []byte(`[
		{"tableName":"B","permissionStateMutation":{"surfaceId":"platform.permission-state.zed.v1","semanticFields":["Z","A"]}},
		{"tableName":"A","permissionStateMutation":{"surfaceId":"platform.permission-state.alpha.v1","semanticFields":["B"]}}
	]`)
	inventory, err := permissionStateMutationInventoryFromCatalog(raw, definitions)
	if err != nil {
		t.Fatal(err)
	}
	surfaces := inventory.Surfaces()
	if len(surfaces) != 2 ||
		surfaces[0].SurfaceID != "platform.permission-state.alpha.v1" ||
		surfaces[1].SurfaceID != "platform.permission-state.zed.v1" ||
		strings.Join(surfaces[1].SemanticFields, ",") != "A,Z" {
		t.Fatalf("surfaces are not canonical: %+v", surfaces)
	}
}
