package sfcrmappservice

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"slices"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	querysemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
	recordsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordaccessv1"
	searchsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/searchv1"
)

const (
	searchScopeComponentKind      = "metadata.pack.search-scope-order.v1"
	searchScopeBusinessKey        = "crm_search_scope_order"
	searchObjectSpecComponentKind = "metadata.pack.object-spec.v1"
	searchObjectSpecBusinessKey   = "standard_objects_spec:crm"
)

// Shared legacy permission fixtures are also used by unrelated V1 consumers.
// Materialize their test declarations as a V3 installed snapshot for scope tests.
func permissionFixtureV3(legacy catalogsemantic.AppMetadataView) (catalogsemantic.AppMetadataViewV3, error) {
	if err := catalogsemantic.ValidateAppMetadataView(legacy); err != nil {
		return catalogsemantic.AppMetadataViewV3{}, err
	}
	view := catalogsemantic.AppMetadataViewV3{
		OrgID: legacy.OrgID, AppInstallationID: legacy.AppInstallationID, AppID: legacy.AppID,
		AppReleaseDigest: legacy.AppReleaseDigest, Lineage: legacy.Lineage, AppComponents: legacy.Components, Receipts: legacy.Receipts,
	}
	for _, source := range legacy.Components {
		var kind, key string
		var format uint32 = 1
		behavior := source.CanonicalBehavior
		switch source.ComponentKind {
		case searchScopeComponentKind:
			kind, key = catalogsemantic.EffectiveSearchScopeComponentKind, catalogsemantic.EffectiveSearchScopeComponentBusinessKey
		case searchObjectSpecComponentKind:
			kind, key = catalogsemantic.EffectiveObjectSearchComponentKind, catalogsemantic.EffectiveObjectSearchComponentBusinessKey
			format = effectiveObjectSearchComponentFormatV4
			var objects []map[string]any
			if err := json.Unmarshal(behavior, &objects); err != nil {
				return view, err
			}
			for _, object := range objects {
				object["tableType"] = "standard_object"
			}
			var err error
			behavior, err = json.Marshal(map[string]any{"objects": objects})
			if err != nil {
				return view, err
			}
		default:
			continue
		}
		view.EffectiveComponents = append(view.EffectiveComponents, catalogsemantic.EffectiveMetadataComponent{
			ComponentKind: kind, ComponentFormatVersion: format, BusinessKey: key, CanonicalBehavior: behavior,
			ContentDigest: catalogsemantic.ComputeEffectiveMetadataComponentDigest(behavior),
		})
	}
	slices.SortFunc(view.EffectiveComponents, func(a, b catalogsemantic.EffectiveMetadataComponent) int {
		if c := strings.Compare(a.ComponentKind, b.ComponentKind); c != 0 {
			return c
		}
		return strings.Compare(a.BusinessKey, b.BusinessKey)
	})
	view.Receipts.CapabilityVersion = catalogsemantic.AppMetadataV3CapabilityName
	view.Lineage.ViewDigest = catalogsemantic.ComputeAppMetadataViewV3Digest(view)
	return view, nil
}

type legacyMetadataFixtureFunc func(context.Context, catalogsemantic.GetRequest) (catalogsemantic.AppMetadataView, error)

func permissionMetadataServiceConfig(
	readPermissions RuntimeReadEffectivePermissionsFunc,
	readMetadata legacyMetadataFixtureFunc,
) ServiceConfig {
	var readV3 RuntimeGetAppMetadataViewV3Func
	if readMetadata != nil {
		readV3 = func(ctx context.Context, request catalogsemantic.GetRequest) (catalogsemantic.AppMetadataViewV3, error) {
			view, err := readMetadata(ctx, request)
			if err != nil {
				return catalogsemantic.AppMetadataViewV3{}, err
			}
			return permissionFixtureV3(view)
		}
	}
	return ServiceConfig{
		RuntimeEvaluate: func(
			context.Context,
			string,
			recordsemantic.Request,
		) (recordsemantic.Response, error) {
			return recordsemantic.Response{}, nil
		},
		RuntimeReady: func() bool { return true },
		RuntimeMetrics: func() runtimegrpc.RuntimeClientMetrics {
			return runtimegrpc.RuntimeClientMetrics{}
		},
		RuntimeQuery: func(
			context.Context,
			string,
			querysemantic.Request,
		) (querysemantic.Response, error) {
			return querysemantic.Response{}, nil
		},
		RuntimeHistoryQuery: func(
			context.Context,
			string,
			querysemantic.HistoryRequest,
		) (querysemantic.HistoryResponse, error) {
			return querysemantic.HistoryResponse{}, nil
		},
		RuntimeReadEffectivePermissions: readPermissions,
		RuntimeGetAppMetadataViewV3:     readV3,
		RequestTimeout:                  time.Second,
		MaxRequestBytes:                 64 * 1024,
	}
}

func newPermissionMetadataTestService(
	t *testing.T,
	readPermissions RuntimeReadEffectivePermissionsFunc,
	readMetadata legacyMetadataFixtureFunc,
) *Service {
	t.Helper()
	config := permissionMetadataServiceConfig(
		func(
			ctx context.Context,
			bearer string,
			request permissionsemantic.Request,
		) (permissionsemantic.Response, error) {
			response, err := readPermissions(ctx, bearer, request)
			if err != nil {
				return permissionsemantic.Response{}, err
			}
			return selectedPermissionTestResponse(response, request), nil
		},
		readMetadata,
	)
	service, err := NewService(config)
	if err != nil {
		t.Fatal(err)
	}
	return service
}

func selectedPermissionTestResponse(
	response permissionsemantic.Response,
	request permissionsemantic.Request,
) permissionsemantic.Response {
	if request.Selection == nil {
		return response
	}
	selection := request.Selection
	if !selection.Requests(permissionsemantic.SectionSystemPermissions) {
		response.SystemPermissions = nil
	}
	if !selection.Requests(permissionsemantic.SectionCustomPermissions) {
		response.CustomPermissions = nil
	}
	if !selection.Requests(permissionsemantic.SectionApplicationPermissions) {
		response.ApplicationPermissions = nil
	}
	if !selection.Requests(permissionsemantic.SectionNavigationPermissions) {
		response.NavigationPermissions = nil
	}
	resources := make(map[string]struct{}, len(selection.ResourceTypes))
	for _, resource := range selection.ResourceTypes {
		resources[strings.ToLower(resource)] = struct{}{}
	}
	if !selection.Requests(permissionsemantic.SectionObjectPermissions) {
		response.ObjectPermissions = nil
	} else {
		response.ObjectPermissions = filterPermissionTestSlice(response.ObjectPermissions, func(permission permissionsemantic.ObjectPermission) bool {
			_, selected := resources[strings.ToLower(permission.ResourceType)]
			return selected
		})
	}
	if !selection.Requests(permissionsemantic.SectionFieldPermissions) {
		response.FieldPermissions = nil
	} else {
		response.FieldPermissions = filterPermissionTestSlice(response.FieldPermissions, func(permission permissionsemantic.FieldPermission) bool {
			_, selected := resources[strings.ToLower(permission.ResourceType)]
			return selected
		})
	}
	if !selection.Requests(permissionsemantic.SectionRecordTypePermissions) {
		response.RecordTypePermissions = nil
	} else {
		response.RecordTypePermissions = filterPermissionTestSlice(response.RecordTypePermissions, func(permission permissionsemantic.RecordTypePermission) bool {
			_, selected := resources[strings.ToLower(permission.ResourceType)]
			return selected
		})
	}
	return response
}

func TestEffectivePermissionSelectionBatchesCatalogResources(t *testing.T) {
	resources := make([]string, 130)
	for index := range resources {
		resources[index] = fmt.Sprintf("Object%03d", index)
	}
	var batchSizes []int
	read := func(
		_ context.Context,
		_ string,
		request permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		if request.Selection == nil || !slices.Equal(
			request.Selection.Sections,
			[]permissionsemantic.Section{permissionsemantic.SectionObjectPermissions},
		) {
			t.Fatalf("selection = %#v", request.Selection)
		}
		batchSizes = append(batchSizes, len(request.Selection.ResourceTypes))
		permissions := make([]permissionsemantic.ObjectPermission, 0, len(request.Selection.ResourceTypes))
		for _, resource := range request.Selection.ResourceTypes {
			permissions = append(permissions, permissionsemantic.ObjectPermission{
				ResourceType: resource, CanRead: true,
			})
		}
		return permissionsemantic.Response{
			ObjectPermissions: permissions,
			Receipts: commonsemantic.Receipts{
				ProtocolVersion: "1.0", CatalogRevision: 17,
				SchemaGeneration: 11, EngineGeneration: 13, SecurityEpoch: 3,
				CapabilityVersion: permissionsemantic.CapabilityName,
			},
		}, nil
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	result, err := readEffectivePermissionSelection(
		ctx, read, "bearer", "", 17,
		[]permissionsemantic.Section{permissionsemantic.SectionObjectPermissions},
		resources,
	)
	if err != nil {
		t.Fatal(err)
	}
	if !slices.Equal(batchSizes, []int{64, 64, 2}) ||
		len(result.ObjectPermissions) != len(resources) ||
		result.ObjectPermissions[0].ResourceType != "Object000" ||
		result.ObjectPermissions[len(result.ObjectPermissions)-1].ResourceType != "Object129" {
		t.Fatalf("batch sizes=%v result=%#v", batchSizes, result.ObjectPermissions)
	}
}

func TestEffectivePermissionSelectionSplitsOnlyOversizedSemanticResponses(t *testing.T) {
	resources := make([]string, 20)
	for index := range resources {
		resources[index] = fmt.Sprintf("Object%03d", index)
	}
	var batchSizes []int
	read := func(
		_ context.Context,
		_ string,
		request permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		batchSizes = append(batchSizes, len(request.Selection.ResourceTypes))
		if len(request.Selection.ResourceTypes) > 8 {
			return permissionsemantic.Response{}, &commonsemantic.Failure{
				Category:  commonsemantic.FailureResourceExhausted,
				LimitKind: commonsemantic.LimitSemanticResponse,
			}
		}
		permissions := make([]permissionsemantic.ObjectPermission, 0, len(request.Selection.ResourceTypes))
		for _, resource := range request.Selection.ResourceTypes {
			permissions = append(permissions, permissionsemantic.ObjectPermission{ResourceType: resource})
		}
		return permissionsemantic.Response{
			ObjectPermissions: permissions,
			Receipts: commonsemantic.Receipts{
				ProtocolVersion: "1.0", CatalogRevision: 17,
				SchemaGeneration: 11, EngineGeneration: 13, SecurityEpoch: 3,
				CapabilityVersion: permissionsemantic.CapabilityName,
			},
		}, nil
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	result, err := readEffectivePermissionSelection(
		ctx, read, "bearer", "", 17,
		[]permissionsemantic.Section{permissionsemantic.SectionObjectPermissions},
		resources,
	)
	if err != nil {
		t.Fatal(err)
	}
	if !slices.Equal(batchSizes, []int{20, 10, 5, 5, 10, 5, 5}) ||
		len(result.ObjectPermissions) != 20 {
		t.Fatalf("batch sizes=%v permissions=%d", batchSizes, len(result.ObjectPermissions))
	}
}

func TestEffectivePermissionSelectionRejectsCrossBatchSecurityDrift(t *testing.T) {
	resources := make([]string, 65)
	for index := range resources {
		resources[index] = fmt.Sprintf("Object%03d", index)
	}
	var calls int
	read := func(
		_ context.Context,
		_ string,
		request permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		calls++
		epoch := uint64(3)
		if calls == 2 {
			epoch = 4
		}
		permissions := make([]permissionsemantic.ObjectPermission, 0, len(request.Selection.ResourceTypes))
		for _, resource := range request.Selection.ResourceTypes {
			permissions = append(permissions, permissionsemantic.ObjectPermission{ResourceType: resource})
		}
		return permissionsemantic.Response{
			ObjectPermissions: permissions,
			Receipts: commonsemantic.Receipts{
				ProtocolVersion: "1.0", CatalogRevision: 17,
				SchemaGeneration: 11, EngineGeneration: 13, SecurityEpoch: epoch,
				CapabilityVersion: permissionsemantic.CapabilityName,
			},
		}, nil
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	_, err := readEffectivePermissionSelection(
		ctx, read, "bearer", "", 17,
		[]permissionsemantic.Section{permissionsemantic.SectionObjectPermissions},
		resources,
	)
	var failure *commonsemantic.Failure
	if !errors.As(err, &failure) || failure.Category != commonsemantic.FailureStaleState ||
		failure.StaleAxis != commonsemantic.StaleAxisSecurity ||
		failure.RequiredRevision != 3 || failure.ServedRevision != 4 {
		t.Fatalf("error = %#v", err)
	}
}

func filterPermissionTestSlice[T any](values []T, keep func(T) bool) []T {
	result := make([]T, 0, len(values))
	for _, value := range values {
		if keep(value) {
			result = append(result, value)
		}
	}
	return result
}

func newRuntimeSearchScopeTestService(
	t *testing.T,
	readSearch RuntimeReadSearchScopeOrderFunc,
	readMetadata legacyMetadataFixtureFunc,
) *Service {
	t.Helper()
	config := permissionMetadataServiceConfig(
		func(
			context.Context,
			string,
			permissionsemantic.Request,
		) (permissionsemantic.Response, error) {
			return validPermissionResponse(17, 3), nil
		},
		readMetadata,
	)
	config.RuntimeReadSearchScopeOrder = readSearch
	service, err := NewService(config)
	if err != nil {
		t.Fatal(err)
	}
	return service
}

func validPermissionResponse(revision, securityEpoch uint64) permissionsemantic.Response {
	return permissionsemantic.Response{
		SystemPermissions: []permissionsemantic.PermissionGrant{
			{PermissionID: "ModifyAllData", Granted: false},
			{PermissionID: "ViewSetup", Granted: true},
		},
		CustomPermissions: []permissionsemantic.PermissionGrant{
			{PermissionID: "CRMExport", Granted: false},
		},
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{
				ResourceType: "Account",
				CanRead:      true, CanCreate: true, CanEdit: true,
				CanDelete: false, CanDeactivate: true,
			},
			{ResourceType: "Campaign", CanRead: false},
			{ResourceType: "Lead", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{
				ResourceType: "Account", FieldName: "Name",
				CanRead: true, CanEdit: false, CanCreate: false,
			},
		},
		Receipts: commonsemantic.Receipts{
			ProtocolVersion:   "1.0",
			CapabilityVersion: permissionsemantic.CapabilityName,
			CatalogRevision:   revision,
			SchemaGeneration:  8,
			EngineGeneration:  9,
			SecurityEpoch:     securityEpoch,
		},
	}
}

func validSearchScopeResponse(
	revision, securityEpoch uint64,
	objectIDs ...string,
) searchsemantic.ScopeOrderResponse {
	return searchsemantic.ScopeOrderResponse{
		ObjectIDs: objectIDs,
		Receipts: commonsemantic.Receipts{
			ProtocolVersion:   "1.0",
			CapabilityVersion: searchsemantic.CapabilityName,
			CatalogRevision:   revision,
			SchemaGeneration:  8,
			EngineGeneration:  9,
			SecurityEpoch:     securityEpoch,
		},
	}
}

func validSearchScopeView(
	t *testing.T,
	revision, securityEpoch uint64,
) catalogsemantic.AppMetadataView {
	t.Helper()
	behavior := []byte(
		`[{"name":"Account","label":"Account"},` +
			`{"name":"Campaign","label":"Campaign"},` +
			`{"name":"Lead","label":"Lead"}]`,
	)
	component := catalogsemantic.AppMetadataComponent{
		ComponentKind:          searchScopeComponentKind,
		ComponentFormatVersion: 1,
		NamespaceID:            AppID,
		BusinessKey:            searchScopeBusinessKey,
		DefinitionVersion:      "1",
		CanonicalBehavior:      behavior,
	}
	component.ComponentID = appauthority.ComponentID(
		component.NamespaceID,
		component.ComponentKind,
		component.BusinessKey,
	)
	var err error
	component.ContentDigest, err = appauthority.ComponentDigest(
		component.ComponentKind,
		component.ComponentFormatVersion,
		component.CanonicalBehavior,
	)
	if err != nil {
		t.Fatal(err)
	}
	objectBehavior := []byte(
		`[` +
			`{"apiName":"Account","keyPrefix":"001","label":"Account","fields":[]},` +
			`{"apiName":"Campaign","keyPrefix":"701","label":"Campaign","fields":[]},` +
			`{"apiName":"Lead","keyPrefix":"00Q","label":"Lead","fields":[]}` +
			`]`,
	)
	objectComponent := catalogsemantic.AppMetadataComponent{
		ComponentKind:          searchObjectSpecComponentKind,
		ComponentFormatVersion: 1,
		NamespaceID:            AppID,
		BusinessKey:            searchObjectSpecBusinessKey,
		DefinitionVersion:      "1",
		CanonicalBehavior:      objectBehavior,
	}
	objectComponent.ComponentID = appauthority.ComponentID(
		objectComponent.NamespaceID,
		objectComponent.ComponentKind,
		objectComponent.BusinessKey,
	)
	objectComponent.ContentDigest, err = appauthority.ComponentDigest(
		objectComponent.ComponentKind,
		objectComponent.ComponentFormatVersion,
		objectComponent.CanonicalBehavior,
	)
	if err != nil {
		t.Fatal(err)
	}
	components := []catalogsemantic.AppMetadataComponent{
		component,
		objectComponent,
	}
	slices.SortFunc(
		components,
		func(left, right catalogsemantic.AppMetadataComponent) int {
			return strings.Compare(left.ComponentID, right.ComponentID)
		},
	)
	view := catalogsemantic.AppMetadataView{
		OrgID:             "org-1",
		AppInstallationID: "01900000-0000-7000-8000-000000000001",
		AppID:             AppID,
		AppReleaseDigest: "sha256:" +
			strings.Repeat("d", 64),
		Lineage: catalogsemantic.ViewLineage{
			CatalogRevision:      revision,
			SchemaGeneration:     8,
			EngineGeneration:     9,
			SourceSnapshotDigest: "sha256:" + strings.Repeat("b", 64),
			ViewDigest:           "sha256:" + strings.Repeat("c", 64),
		},
		Components: components,
		Receipts: commonsemantic.Receipts{
			ProtocolVersion:   "1.0",
			CapabilityVersion: catalogsemantic.AppMetadataCapabilityName,
			CatalogRevision:   revision,
			SchemaGeneration:  8,
			EngineGeneration:  9,
			SecurityEpoch:     securityEpoch,
		},
	}
	view.Lineage.ViewDigest = catalogsemantic.ComputeAppMetadataViewDigest(view)
	if err := catalogsemantic.ValidateAppMetadataView(view); err != nil {
		t.Fatalf("test AppMetadataView is invalid: %v", err)
	}
	return view
}

func appMetadataComponentIndex(
	t *testing.T,
	view catalogsemantic.AppMetadataView,
	kind string,
	businessKey string,
) int {
	t.Helper()
	for index, component := range view.Components {
		if component.ComponentKind == kind &&
			component.BusinessKey == businessKey {
			return index
		}
	}
	t.Fatalf("component %s/%s is absent", kind, businessKey)
	return -1
}

func permissionMetadataRequest(method, path string) *http.Request {
	request := httptest.NewRequest(method, path, nil)
	request.Header.Set("Authorization", "Bearer user-secret")
	return request
}

func TestEffectivePermissionsOwnsCRMProjection(t *testing.T) {
	var calls atomic.Uint64
	service := newPermissionMetadataTestService(
		t,
		func(
			_ context.Context,
			bearer string,
			request permissionsemantic.Request,
		) (permissionsemantic.Response, error) {
			calls.Add(1)
			if bearer != "user-secret" ||
				request.TargetPrincipalID != "user-2" ||
				request.MinCatalogRevision == nil || *request.MinCatalogRevision != 17 ||
				request.Selection == nil {
				t.Fatalf("bearer=%q request=%+v", bearer, request)
			}
			return validPermissionResponse(17, 3), nil
		},
		func(
			context.Context,
			catalogsemantic.GetRequest,
		) (catalogsemantic.AppMetadataView, error) {
			return validSearchScopeView(t, 17, 3), nil
		},
	)
	recorder := httptest.NewRecorder()
	service.ServeHTTP(
		recorder,
		permissionMetadataRequest(
			http.MethodGet,
			"/services/data/v63.0/permissions/effective?userId=user-2",
		),
	)
	const want = `{"customPermissions":{"CRMExport":false},` +
		`"fieldPermissions":[{"Field":"Account.Name","PermissionsEdit":false,` +
		`"PermissionsRead":true,"SobjectType":"Account"}],` +
		`"objectPermissions":[{"PermissionsCreate":true,"PermissionsDelete":false,` +
		`"PermissionsEdit":true,"PermissionsModifyAllRecords":false,` +
		`"PermissionsRead":true,"PermissionsViewAllRecords":false,` +
		`"SobjectType":"Account"},{"PermissionsCreate":false,` +
		`"PermissionsDelete":false,"PermissionsEdit":false,` +
		`"PermissionsModifyAllRecords":false,"PermissionsRead":false,` +
		`"PermissionsViewAllRecords":false,"SobjectType":"Campaign"},` +
		`{"PermissionsCreate":false,"PermissionsDelete":false,` +
		`"PermissionsEdit":false,"PermissionsModifyAllRecords":false,` +
		`"PermissionsRead":true,"PermissionsViewAllRecords":false,` +
		`"SobjectType":"Lead"}],` +
		`"systemPermissions":{"ModifyAllData":false,"ViewSetup":true}}` + "\n"
	if recorder.Code != http.StatusOK || recorder.Body.String() != want ||
		calls.Load() != 2 || strings.Contains(recorder.Body.String(), "CanDeactivate") {
		t.Fatalf(
			"status=%d calls=%d body=%s",
			recorder.Code,
			calls.Load(),
			recorder.Body.String(),
		)
	}
}

func TestEffectivePermissionsWireQualifiesFieldNamesIdempotently(t *testing.T) {
	for _, test := range []struct {
		name         string
		resourceType string
		fieldName    string
		want         string
	}{
		{
			name:         "raw",
			resourceType: "Account",
			fieldName:    "Description",
			want:         "Account.Description",
		},
		{
			name:         "already qualified",
			resourceType: "Account",
			fieldName:    "Account.Description",
			want:         "Account.Description",
		},
		{
			name:         "mismatched qualifier cannot replace resource authority",
			resourceType: "Account",
			fieldName:    "Contact.Description",
			want:         "Account.Description",
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			if got := qualifiedPermissionField(
				test.resourceType,
				test.fieldName,
			); got != test.want {
				t.Fatalf("qualified field = %q, want %q", got, test.want)
			}
		})
	}
}

func TestSearchScopeOrderUsesOneCatalogRevisionAndFiltersByRead(t *testing.T) {
	view := validSearchScopeView(t, 17, 3)
	var metadataCalls atomic.Uint64
	var permissionCalls atomic.Uint64
	service := newPermissionMetadataTestService(
		t,
		func(
			_ context.Context,
			bearer string,
			request permissionsemantic.Request,
		) (permissionsemantic.Response, error) {
			permissionCalls.Add(1)
			if bearer != "user-secret" || request.TargetPrincipalID != "" ||
				request.MinCatalogRevision == nil ||
				*request.MinCatalogRevision != 17 || request.Selection == nil ||
				!slices.Equal(request.Selection.Sections, []permissionsemantic.Section{
					permissionsemantic.SectionObjectPermissions,
				}) || !slices.Equal(request.Selection.ResourceTypes, []string{
				"Account", "Campaign", "Lead",
			}) {
				t.Fatalf("bearer=%q request=%+v", bearer, request)
			}
			return validPermissionResponse(17, 3), nil
		},
		func(
			_ context.Context,
			request catalogsemantic.GetRequest,
		) (catalogsemantic.AppMetadataView, error) {
			metadataCalls.Add(1)
			if request.MinCatalogRevision != nil {
				t.Fatalf("AppMetadata request=%+v", request)
			}
			return view, nil
		},
	)
	recorder := httptest.NewRecorder()
	service.ServeHTTP(
		recorder,
		permissionMetadataRequest(
			http.MethodGet,
			"/services/data/v59.0/metadata/search/scope-order",
		),
	)
	if recorder.Code != http.StatusOK ||
		recorder.Body.String() != "[\"Account\",\"Lead\"]\n" ||
		metadataCalls.Load() != 1 || permissionCalls.Load() != 1 {
		t.Fatalf(
			"status=%d metadata=%d permissions=%d body=%s",
			recorder.Code,
			metadataCalls.Load(),
			permissionCalls.Load(),
			recorder.Body.String(),
		)
	}
}

func TestRuntimeSearchScopeOrderUsesOneCatalogRevisionAndOwnsV63Projection(
	t *testing.T,
) {
	view := validSearchScopeView(t, 17, 3)
	var metadataCalls atomic.Uint64
	var searchCalls atomic.Uint64
	service := newRuntimeSearchScopeTestService(
		t,
		func(
			_ context.Context,
			bearer string,
			request searchsemantic.ScopeOrderRequest,
		) (searchsemantic.ScopeOrderResponse, error) {
			searchCalls.Add(1)
			if bearer != "user-secret" ||
				strings.Join(request.ObjectIDs, ",") != "Account,Campaign,Lead" ||
				request.MinCatalogRevision == nil ||
				*request.MinCatalogRevision != 17 {
				t.Fatalf("bearer=%q request=%+v", bearer, request)
			}
			return validSearchScopeResponse(17, 3, "Lead", "Account"), nil
		},
		func(
			_ context.Context,
			request catalogsemantic.GetRequest,
		) (catalogsemantic.AppMetadataView, error) {
			metadataCalls.Add(1)
			if request.MinCatalogRevision != nil {
				t.Fatalf("AppMetadata request=%+v", request)
			}
			return view, nil
		},
	)
	recorder := httptest.NewRecorder()
	service.ServeHTTP(
		recorder,
		permissionMetadataRequest(
			http.MethodGet,
			"/services/data/v59.0/search/scopeOrder",
		),
	)
	want := `[{"type":"Lead","url":"/services/data/v59.0/sobjects/Lead/describe","name":"Lead","label":"Lead","keyPrefix":"00Q"},{"type":"Account","url":"/services/data/v59.0/sobjects/Account/describe","name":"Account","label":"Account","keyPrefix":"001"}]` + "\n"
	if recorder.Code != http.StatusOK || recorder.Body.String() != want ||
		metadataCalls.Load() != 1 || searchCalls.Load() != 1 {
		t.Fatalf(
			"status=%d metadata=%d search=%d body=%s",
			recorder.Code,
			metadataCalls.Load(),
			searchCalls.Load(),
			recorder.Body.String(),
		)
	}
}

func TestRuntimeSearchScopeOrderFailsClosedOnMixedOrMissingAuthority(
	t *testing.T,
) {
	for _, test := range []struct {
		name           string
		mutateView     func(*catalogsemantic.AppMetadataView)
		mutateResponse func(*searchsemantic.ScopeOrderResponse)
	}{
		{
			name: "catalog revision",
			mutateResponse: func(response *searchsemantic.ScopeOrderResponse) {
				response.Receipts.CatalogRevision++
			},
		},
		{
			name: "security epoch",
			mutateResponse: func(response *searchsemantic.ScopeOrderResponse) {
				response.Receipts.SecurityEpoch++
			},
		},
		{
			name: "schema generation",
			mutateResponse: func(response *searchsemantic.ScopeOrderResponse) {
				response.Receipts.SchemaGeneration++
			},
		},
		{
			name: "engine generation",
			mutateResponse: func(response *searchsemantic.ScopeOrderResponse) {
				response.Receipts.EngineGeneration++
			},
		},
		{
			name: "wrong app",
			mutateView: func(view *catalogsemantic.AppMetadataView) {
				view.AppID = "io.nexuscrm.other"
				view.Lineage.ViewDigest =
					catalogsemantic.ComputeAppMetadataViewDigest(*view)
			},
		},
		{
			name: "missing object authority",
			mutateView: func(view *catalogsemantic.AppMetadataView) {
				index := appMetadataComponentIndex(
					t,
					*view,
					searchObjectSpecComponentKind,
					searchObjectSpecBusinessKey,
				)
				view.Components = slices.Delete(
					view.Components,
					index,
					index+1,
				)
				view.Lineage.ViewDigest =
					catalogsemantic.ComputeAppMetadataViewDigest(*view)
			},
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			view := validSearchScopeView(t, 17, 3)
			response := validSearchScopeResponse(17, 3, "Account")
			if test.mutateView != nil {
				test.mutateView(&view)
			}
			if test.mutateResponse != nil {
				test.mutateResponse(&response)
			}
			service := newRuntimeSearchScopeTestService(
				t,
				func(
					context.Context,
					string,
					searchsemantic.ScopeOrderRequest,
				) (searchsemantic.ScopeOrderResponse, error) {
					return response, nil
				},
				func(
					context.Context,
					catalogsemantic.GetRequest,
				) (catalogsemantic.AppMetadataView, error) {
					return view, nil
				},
			)
			recorder := httptest.NewRecorder()
			service.ServeHTTP(
				recorder,
				permissionMetadataRequest(
					http.MethodGet,
					"/services/data/v63.0/search/scopeOrder",
				),
			)
			if recorder.Code != http.StatusServiceUnavailable {
				t.Fatalf(
					"status=%d body=%s",
					recorder.Code,
					recorder.Body.String(),
				)
			}
		})
	}
}

func TestRuntimeSearchScopeOrderHasNoLocalFallbackAndPropagatesCancellation(
	t *testing.T,
) {
	var legacyCalls atomic.Uint64
	service := newPermissionMetadataTestService(
		t,
		func(
			context.Context,
			string,
			permissionsemantic.Request,
		) (permissionsemantic.Response, error) {
			legacyCalls.Add(1)
			return validPermissionResponse(17, 3), nil
		},
		func(
			context.Context,
			catalogsemantic.GetRequest,
		) (catalogsemantic.AppMetadataView, error) {
			return validSearchScopeView(t, 17, 3), nil
		},
	)
	recorder := httptest.NewRecorder()
	service.ServeHTTP(
		recorder,
		permissionMetadataRequest(
			http.MethodGet,
			"/services/data/v63.0/search/scopeOrder",
		),
	)
	if recorder.Code != http.StatusNotFound || legacyCalls.Load() != 0 {
		t.Fatalf(
			"without Runtime Search status=%d legacy calls=%d body=%s",
			recorder.Code,
			legacyCalls.Load(),
			recorder.Body.String(),
		)
	}

	canceled := make(chan struct{})
	service = newRuntimeSearchScopeTestService(
		t,
		func(
			ctx context.Context,
			_ string,
			_ searchsemantic.ScopeOrderRequest,
		) (searchsemantic.ScopeOrderResponse, error) {
			<-ctx.Done()
			close(canceled)
			return searchsemantic.ScopeOrderResponse{}, &commonsemantic.Failure{
				Category: commonsemantic.FailureCanceled,
			}
		},
		func(
			context.Context,
			catalogsemantic.GetRequest,
		) (catalogsemantic.AppMetadataView, error) {
			return validSearchScopeView(t, 17, 3), nil
		},
	)
	ctx, cancel := context.WithCancel(context.Background())
	request := permissionMetadataRequest(
		http.MethodGet,
		"/services/data/v63.0/search/scopeOrder",
	).WithContext(ctx)
	cancel()
	recorder = httptest.NewRecorder()
	service.ServeHTTP(recorder, request)
	select {
	case <-canceled:
	default:
		t.Fatal("Runtime Search port did not observe caller cancellation")
	}
	if recorder.Code != 499 {
		t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
	}
}

func TestSearchScopeOrderFailsClosedOnMixedOrMissingAuthority(t *testing.T) {
	for _, test := range []struct {
		name        string
		mutateView  func(*catalogsemantic.AppMetadataView)
		mutatePerms func(*permissionsemantic.Response)
	}{
		{
			name: "catalog revision",
			mutatePerms: func(response *permissionsemantic.Response) {
				response.Receipts.CatalogRevision++
			},
		},
		{
			name: "security epoch",
			mutatePerms: func(response *permissionsemantic.Response) {
				response.Receipts.SecurityEpoch++
			},
		},
		{
			name: "schema generation",
			mutatePerms: func(response *permissionsemantic.Response) {
				response.Receipts.SchemaGeneration++
			},
		},
		{
			name: "wrong app",
			mutateView: func(view *catalogsemantic.AppMetadataView) {
				view.AppID = "io.nexuscrm.other"
				view.Lineage.ViewDigest =
					catalogsemantic.ComputeAppMetadataViewDigest(*view)
			},
		},
		{
			name: "missing component",
			mutateView: func(view *catalogsemantic.AppMetadataView) {
				view.Components = nil
				view.Lineage.ViewDigest =
					catalogsemantic.ComputeAppMetadataViewDigest(*view)
			},
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			view := validSearchScopeView(t, 17, 3)
			response := validPermissionResponse(17, 3)
			if test.mutateView != nil {
				test.mutateView(&view)
			}
			if test.mutatePerms != nil {
				test.mutatePerms(&response)
			}
			service := newPermissionMetadataTestService(
				t,
				func(
					context.Context,
					string,
					permissionsemantic.Request,
				) (permissionsemantic.Response, error) {
					return response, nil
				},
				func(
					context.Context,
					catalogsemantic.GetRequest,
				) (catalogsemantic.AppMetadataView, error) {
					return view, nil
				},
			)
			recorder := httptest.NewRecorder()
			service.ServeHTTP(
				recorder,
				permissionMetadataRequest(
					http.MethodGet,
					"/services/data/v63.0/metadata/search/scope-order",
				),
			)
			if recorder.Code != http.StatusServiceUnavailable {
				t.Fatalf(
					"status=%d body=%s",
					recorder.Code,
					recorder.Body.String(),
				)
			}
		})
	}
}

func TestPermissionMetadataRejectsPartialPortsAndLocalRequests(t *testing.T) {
	readPermissions := RuntimeReadEffectivePermissionsFunc(func(
		context.Context,
		string,
		permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		return validPermissionResponse(17, 3), nil
	})
	readMetadata := legacyMetadataFixtureFunc(func(
		context.Context,
		catalogsemantic.GetRequest,
	) (catalogsemantic.AppMetadataView, error) {
		return validSearchScopeView(t, 17, 3), nil
	})
	missingV3 := permissionMetadataServiceConfig(readPermissions, readMetadata)
	missingV3.RuntimeGetAppMetadataViewV3 = nil
	for _, config := range []ServiceConfig{
		missingV3,
		permissionMetadataServiceConfig(readPermissions, nil),
	} {
		if service, err := NewService(config); err == nil || service != nil {
			t.Fatalf("NewService(partial ports)=%#v, %v; want rejection", service, err)
		}
	}
	searchOnly := permissionMetadataServiceConfig(nil, nil)
	searchOnly.RuntimeReadSearchScopeOrder = func(
		context.Context,
		string,
		searchsemantic.ScopeOrderRequest,
	) (searchsemantic.ScopeOrderResponse, error) {
		return validSearchScopeResponse(17, 3, "Account"), nil
	}
	if service, err := NewService(searchOnly); err == nil || service != nil {
		t.Fatalf(
			"NewService(search without metadata)=%#v, %v; want rejection",
			service,
			err,
		)
	}

	var calls atomic.Uint64
	service := newPermissionMetadataTestService(
		t,
		func(
			context.Context,
			string,
			permissionsemantic.Request,
		) (permissionsemantic.Response, error) {
			calls.Add(1)
			return validPermissionResponse(17, 3), nil
		},
		readMetadata,
	)
	for _, request := range []*http.Request{
		httptest.NewRequest(
			http.MethodGet,
			"/services/data/v63.0/permissions/effective",
			nil,
		),
		permissionMetadataRequest(
			http.MethodPost,
			"/services/data/v63.0/metadata/search/scope-order",
		),
	} {
		recorder := httptest.NewRecorder()
		service.ServeHTTP(recorder, request)
		if recorder.Code != http.StatusUnauthorized &&
			recorder.Code != http.StatusMethodNotAllowed {
			t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
		}
	}
	if calls.Load() != 0 {
		t.Fatalf("Runtime calls=%d, want zero", calls.Load())
	}
}

func TestPermissionMetadataPropagatesCancellation(t *testing.T) {
	canceled := make(chan struct{})
	service := newPermissionMetadataTestService(
		t,
		func(
			ctx context.Context,
			_ string,
			_ permissionsemantic.Request,
		) (permissionsemantic.Response, error) {
			<-ctx.Done()
			close(canceled)
			return permissionsemantic.Response{}, &commonsemantic.Failure{
				Category: commonsemantic.FailureCanceled,
			}
		},
		func(
			context.Context,
			catalogsemantic.GetRequest,
		) (catalogsemantic.AppMetadataView, error) {
			return validSearchScopeView(t, 17, 3), nil
		},
	)
	ctx, cancel := context.WithCancel(context.Background())
	request := permissionMetadataRequest(
		http.MethodGet,
		"/services/data/v63.0/permissions/effective",
	).WithContext(ctx)
	cancel()
	recorder := httptest.NewRecorder()
	service.ServeHTTP(recorder, request)
	select {
	case <-canceled:
	default:
		t.Fatal("Runtime port did not observe caller cancellation")
	}
	if recorder.Code != 499 {
		t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
	}
}

func TestSearchScopeComponentRejectsMalformedBehaviorWithoutFallback(t *testing.T) {
	for _, behavior := range []string{
		`[{"name":"Account","label":"Account","extra":true}]`,
		`[{"name":"Account","label":"Account"},{"name":"account","label":"Duplicate"}]`,
		`[{"name":" Account","label":"Account"}]`,
		`[{"name":"Account","label":""}]`,
		`null`,
		`[] {}`,
	} {
		t.Run(behavior, func(t *testing.T) {
			view := validSearchScopeView(t, 17, 3)
			index := appMetadataComponentIndex(
				t,
				view,
				searchScopeComponentKind,
				searchScopeBusinessKey,
			)
			view.Components[index].CanonicalBehavior = []byte(behavior)
			var err error
			view.Components[index].ContentDigest, err = appauthority.ComponentDigest(
				view.Components[index].ComponentKind,
				view.Components[index].ComponentFormatVersion,
				view.Components[index].CanonicalBehavior,
			)
			if err != nil {
				t.Fatal(err)
			}
			view.Lineage.ViewDigest =
				catalogsemantic.ComputeAppMetadataViewDigest(view)
			if err := catalogsemantic.ValidateAppMetadataView(view); err != nil {
				t.Fatalf("mutated test view is invalid: %v", err)
			}
			effective, err := permissionFixtureV3(view)
			if err != nil {
				t.Fatal(err)
			}
			specs, err := searchObjectSpecsFromView(effective)
			if err != nil {
				t.Fatal(err)
			}
			_, err = effectiveSearchScopeOrder(effective, specs)
			var failure *commonsemantic.Failure
			if !errors.As(err, &failure) ||
				failure.Category != commonsemantic.FailureUnavailable {
				t.Fatalf("error=%v, want fail-closed UNAVAILABLE", err)
			}
		})
	}
}

func TestSearchScopeRejectsMalformedEffectiveObjectShape(t *testing.T) {
	for _, behavior := range []string{
		`[{"apiName":"Account","label":"","fields":[]}]`,
		`[{"apiName":"Account","label":"Account"},{"apiName":"account","label":"Duplicate"}]`,
		`[{"apiName":" Account","label":"Account"}]`,
		`null`, `[] {}`,
	} {
		t.Run(behavior, func(t *testing.T) {
			view := searchTestView(t, []searchObjectSpec{{APIName: "Account", Label: "Account"}})
			view.EffectiveComponents[0].CanonicalBehavior = []byte(`{"objects":` + behavior + `}`)
			view.EffectiveComponents[0].ContentDigest = catalogsemantic.ComputeEffectiveMetadataComponentDigest(view.EffectiveComponents[0].CanonicalBehavior)
			view.Lineage.ViewDigest = catalogsemantic.ComputeAppMetadataViewV3Digest(view)
			client := RuntimePermissionMetadataClient{appMetadata: func(context.Context, catalogsemantic.GetRequest) (catalogsemantic.AppMetadataViewV3, error) {
				return view, nil
			}}
			_, _, _, err := client.readInstalledSearchScope(t.Context())
			recorder := httptest.NewRecorder()
			writeSearchFailure(recorder, err)
			if recorder.Code != http.StatusServiceUnavailable {
				t.Fatalf("status=%d err=%v", recorder.Code, err)
			}
		})
	}
}
