package sfcrmappservice

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"slices"
	"strings"
	"testing"

	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
)

func TestDecodeAppPageLayoutRetainsReleasedOpportunityArtifact(t *testing.T) {
	source, err := os.ReadFile(filepath.Join(
		"..", "..", "salesforce-crm-standard", "layouts", "opportunity_page_layout.json",
	))
	if err != nil {
		t.Fatal(err)
	}
	layout, err := decodeAppPageLayout(source)
	if err != nil {
		t.Fatal(err)
	}
	if layout.ObjectAPIName != "Opportunity" || len(layout.HeaderActions) != 2 ||
		layout.HeaderActions[0].Name != "AddProducts" ||
		layout.HeaderActions[0].Type != "QuickAction" ||
		layout.HeaderActions[0].TargetObject != "" ||
		layout.HeaderActions[0].Config != nil {
		t.Fatalf("released Opportunity layout = %#v", layout)
	}
}

func testMasterRecordTypeProjection() recordTypeProjection {
	return recordTypeProjection{
		entries: []projectedRecordType{{
			ResourceType: "Lead", RecordTypeID: masterRecordTypeID,
			DeveloperName: "Master", Name: "Master", Available: true,
			Master: true, IsDefault: true,
		}},
		defaultRecordTypeID: masterRecordTypeID,
	}
}

func TestAuthorizePageLayoutFiltersParentAndChildFieldAuthority(t *testing.T) {
	tableType := "standard"
	specs := map[string]searchObjectSpec{
		"account": {
			APIName: "Account", TableType: &tableType,
			Fields: []searchFieldSpec{{APIName: "Name"}, {APIName: "Secret"}},
		},
		"contact": {
			APIName: "Contact", TableType: &tableType,
			Fields: []searchFieldSpec{{APIName: "AccountId"}, {APIName: "Name"}, {APIName: "Secret"}},
		},
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Account", CanRead: true},
			{ResourceType: "Contact", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Account", FieldName: "Name", CanRead: true},
			{ResourceType: "Contact", FieldName: "AccountId", CanRead: true},
			{ResourceType: "Contact", FieldName: "Name", CanRead: true},
		},
	}
	layout := appPageLayout{
		ObjectAPIName: "Account",
		CompactLayout: []string{"Name", "Secret"},
		Sections:      []appPageLayoutSection{{Fields: []string{"Name", "Secret"}}},
		RelatedLists: []appRelatedList{{
			ObjectAPIName: "Contact", LookupField: "AccountId", Fields: []string{"Name", "Secret"},
		}},
	}
	authorized, err := authorizePageLayout(layout, specs, permissions, testCurrentListFields(specs))
	if err != nil {
		t.Fatal(err)
	}
	if len(authorized.CompactLayout) != 1 || authorized.CompactLayout[0] != "Name" ||
		len(authorized.Sections) != 1 || len(authorized.Sections[0].Fields) != 1 || authorized.Sections[0].Fields[0] != "Name" ||
		len(authorized.RelatedLists) != 1 || len(authorized.RelatedLists[0].Fields) != 1 || authorized.RelatedLists[0].Fields[0] != "Name" {
		t.Fatalf("authorized layout = %#v", authorized)
	}
}

func TestAuthorizePageLayoutLeavesStandardActionAuthorityWithItsOwner(t *testing.T) {
	tableType := "standard"
	specs := map[string]searchObjectSpec{
		"opportunity": {APIName: "Opportunity", TableType: &tableType},
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{{
			ResourceType: "Opportunity", CanRead: true,
		}},
	}
	layout := appPageLayout{
		ObjectAPIName: "Opportunity",
		HeaderActions: []appLayoutAction{
			{Name: "createQuote", Label: "Create Quote", Type: "Standard"},
			{Name: "ChangeOwner", Label: "Change Owner", Type: "ChangeOwner"},
		},
	}

	authorized, err := authorizePageLayout(layout, specs, permissions, testCurrentListFields(specs))
	if err != nil {
		t.Fatal(err)
	}
	if len(authorized.HeaderActions) != 1 ||
		authorized.HeaderActions[0].Name != "createQuote" {
		t.Fatalf("authorized header actions = %#v", authorized.HeaderActions)
	}
}

func TestDecodeAppPageLayoutRetainsProdLayoutSemantics(t *testing.T) {
	layout, err := decodeAppPageLayout([]byte(`{
		"objectApiName":"Account","layoutName":"Account Layout","isDefault":true,
		"sections":[{
			"id":"summary","label":"Summary","tab":"Details","type":"Component",
			"componentName":"crm.summary","componentConfig":{"field":"Rating"},
			"columns":2,"fields":["Name","Rating"],
			"fieldLabels":{"Rating":"Customer tier"},
			"fieldBehaviors":{"Rating":{"create":"required"}},
			"visibilityCondition":"record.Name != null"
		}],
		"relatedLists":[{
			"id":"contacts","label":"Contacts","objectApiName":"Contact",
			"lookupField":"AccountId","fields":["Name"],
			"prefillMappings":{"AccountId":"Id"}
		}],
		"headerActions":[{"name":"Edit","label":"Edit","type":"Standard","visibilityCondition":"record.Name != null"}],
		"quickActions":[],"headerActionsAuthored":true,"quickActionsAuthored":true,
		"recordStates":[{"id":"open","when":"record.Name != null","priority":10,
			"banner":{"severity":"info","title":"Open"},
			"links":[{"id":"owner","label":"Owner","referenceFieldApiName":"OwnerId","objectApiName":"User"}],
			"actionOverrides":[{"actionKey":"host:edit","mode":"disable"}],
			"readOnly":{"enabled":true,"reason":"Locked"}
		}],
		"primaryHeaderActions":["host:edit"]
	}`))
	if err != nil {
		t.Fatal(err)
	}
	section := layout.Sections[0]
	if section.Type != "Component" || section.ComponentName != "crm.summary" ||
		section.ComponentConfig["field"] != "Rating" || section.FieldLabels["Rating"] != "Customer tier" ||
		section.FieldBehaviors["Rating"]["create"] != "required" ||
		section.VisibilityCondition != "record.Name != null" {
		t.Fatalf("section semantics = %#v", section)
	}
	if layout.RelatedLists[0].PrefillMappings["AccountId"] != "Id" ||
		len(layout.HeaderActions) != 1 || !layout.HeaderActionsAuthored ||
		layout.QuickActions == nil || !layout.QuickActionsAuthored ||
		len(layout.RecordStates) != 1 || layout.RecordStates[0].Links[0].ObjectAPIName != "User" ||
		!slices.Equal(layout.PrimaryHeaderActions, []string{"host:edit"}) {
		t.Fatalf("retained layout = %#v", layout)
	}
}

func TestAuthorizePageLayoutRemovesEveryHiddenFieldReference(t *testing.T) {
	tableType := "standard"
	specs := map[string]searchObjectSpec{
		"account": {
			APIName: "Account", TableType: &tableType,
			Fields: []searchFieldSpec{
				{APIName: "Id"}, {APIName: "Name", Createable: true},
				{APIName: "OwnerId"}, {APIName: "Secret"},
			},
		},
		"contact": {
			APIName: "Contact", TableType: &tableType,
			Fields: []searchFieldSpec{
				{APIName: "AccountId", Createable: true}, {APIName: "Name", Createable: true},
				{APIName: "Secret", Createable: true},
			},
		},
		"user": {APIName: "User", TableType: &tableType},
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Account", CanRead: true, CanEdit: true},
			{ResourceType: "Contact", CanRead: true, CanCreate: true},
			{ResourceType: "User", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Account", FieldName: "Id", CanRead: true},
			{ResourceType: "Account", FieldName: "Name", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: "Account", FieldName: "OwnerId", CanRead: true},
			{ResourceType: "Contact", FieldName: "AccountId", CanRead: true, CanEdit: true, CanCreate: true},
			{ResourceType: "Contact", FieldName: "Name", CanRead: true, CanEdit: true, CanCreate: true},
		},
	}
	layout := appPageLayout{
		ObjectAPIName: "Account", LayoutName: "Account Layout", IsDefault: true,
		Sections: []appPageLayoutSection{
			{ID: "visible", Fields: []string{"Name", "Secret"}, FieldLabels: map[string]string{"Name": "Customer", "Secret": "Hidden"}, FieldBehaviors: map[string]map[string]string{"Name": {"edit": "required"}, "Secret": {"edit": "required"}}},
			{ID: "hidden-expression", Fields: []string{"Name"}, VisibilityCondition: "record.Secret != null"},
			{ID: "hidden-component", Type: "Component", ComponentName: "crm.summary", ComponentConfig: map[string]any{"field": "Secret"}},
		},
		RelatedLists: []appRelatedList{{
			ObjectAPIName: "Contact", LookupField: "AccountId", Fields: []string{"Name", "Secret"},
			PrefillMappings: map[string]string{"AccountId": "Id", "Name": "Secret", "Secret": "Name"},
		}},
		HeaderActions: []appLayoutAction{
			{Name: "Edit", Label: "Edit", Type: "Standard", VisibilityCondition: "record.Name != null"},
			{Name: "Hidden", Label: "Hidden", Type: "Standard", VisibilityCondition: "record.Secret != null"},
		},
		RecordStates: []appRecordStateRule{
			{ID: "visible", When: "record.Name != null", Links: []appRecordStateLink{{ID: "owner", ReferenceFieldAPIName: "OwnerId", ObjectAPIName: "User"}}},
			{ID: "hidden", When: "record.Secret != null"},
		},
		RecordPanels: []appRecordPanel{
			{ID: "visible", RendererID: "crm.visible", Config: map[string]any{"field": "Name"}},
			{ID: "hidden", RendererID: "crm.hidden", Config: map[string]any{"field": "Secret"}},
		},
	}
	authorized, err := authorizePageLayout(layout, specs, permissions, testCurrentListFields(specs))
	if err != nil {
		t.Fatal(err)
	}
	if len(authorized.Sections) != 1 || !slices.Equal(authorized.Sections[0].Fields, []string{"Name"}) ||
		len(authorized.Sections[0].FieldLabels) != 1 || authorized.Sections[0].FieldLabels["Name"] != "Customer" ||
		len(authorized.Sections[0].FieldBehaviors) != 1 || authorized.Sections[0].FieldBehaviors["Name"]["edit"] != "required" {
		t.Fatalf("authorized sections = %#v", authorized.Sections)
	}
	if len(authorized.RelatedLists) != 1 || !slices.Equal(authorized.RelatedLists[0].Fields, []string{"Name"}) ||
		len(authorized.RelatedLists[0].PrefillMappings) != 1 || authorized.RelatedLists[0].PrefillMappings["AccountId"] != "Id" {
		t.Fatalf("authorized related lists = %#v", authorized.RelatedLists)
	}
	if len(authorized.HeaderActions) != 1 || authorized.HeaderActions[0].Name != "Edit" ||
		len(authorized.RecordStates) != 1 || authorized.RecordStates[0].ID != "visible" ||
		len(authorized.RecordPanels) != 1 || authorized.RecordPanels[0].ID != "visible" {
		t.Fatalf(
			"authorized presentation = %#v / %#v / %#v",
			authorized.HeaderActions, authorized.RecordStates, authorized.RecordPanels,
		)
	}
}

func TestAuthorizePageLayoutInheritsReadableChildListFieldsWhenRuleIsEmpty(t *testing.T) {
	tableType := "standard"
	var child searchObjectSpec
	if err := json.Unmarshal([]byte(`{
		"apiName":"Month_End_ARR__c",
		"listFields":["Product_Family__c","Revenue_Month__c","Month_End_ARR_Amount__c","Total_ARR__c","Owner_Name__c"],
		"fields":[
			{"apiName":"Account__c"},
			{"apiName":"Product_Family__c"},
			{"apiName":"Revenue_Month__c"},
			{"apiName":"Month_End_ARR_Amount__c"},
			{"apiName":"Total_ARR__c"},
			{"apiName":"Owner_Name__c"}
		]
	}`), &child); err != nil {
		t.Fatal(err)
	}
	child.TableType = &tableType
	specs := map[string]searchObjectSpec{
		"account":          {APIName: "Account", TableType: &tableType},
		"month_end_arr__c": child,
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Account", CanRead: true},
			{ResourceType: "Month_End_ARR__c", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Month_End_ARR__c", FieldName: "Account__c", CanRead: true},
			{ResourceType: "Month_End_ARR__c", FieldName: "Product_Family__c", CanRead: true},
			{ResourceType: "Month_End_ARR__c", FieldName: "Revenue_Month__c", CanRead: true},
			{ResourceType: "Month_End_ARR__c", FieldName: "Month_End_ARR_Amount__c", CanRead: true},
			{ResourceType: "Month_End_ARR__c", FieldName: "Total_ARR__c", CanRead: true},
			{ResourceType: "Month_End_ARR__c", FieldName: "Owner_Name__c", CanRead: true},
		},
	}
	layout := appPageLayout{
		ObjectAPIName: "Account",
		RelatedLists: []appRelatedList{{
			ObjectAPIName: "Month_End_ARR__c", LookupField: "Account__c",
		}},
	}
	currentListFields := testCurrentListFields(specs)
	currentListFields["month_end_arr__c"] = []string{"Revenue_Month__c", "Product_Family__c"}
	authorized, err := authorizePageLayout(layout, specs, permissions, currentListFields)
	if err != nil {
		t.Fatal(err)
	}
	if len(authorized.RelatedLists) != 1 ||
		!slices.Equal(
			authorized.RelatedLists[0].Fields,
			[]string{"Revenue_Month__c", "Product_Family__c"},
		) {
		t.Fatalf("authorized related lists = %#v", authorized.RelatedLists)
	}
}

func TestPageLayoutPermissionSelectionBatchesRootAndRelatedResources(t *testing.T) {
	standardObject := "standard"
	layout := appPageLayout{
		ObjectAPIName: "Account",
		RelatedLists: []appRelatedList{
			{ObjectAPIName: "Contact"},
			{ObjectAPIName: "Opportunity"},
			{ObjectAPIName: "contact"},
			{ObjectAPIName: "Missing"},
		},
		HeaderActions: []appLayoutAction{{
			Name: "CreateTask", Type: "NavigateToNewRecord", TargetObject: "Task",
		}},
		RecordStates: []appRecordStateRule{{Links: []appRecordStateLink{{ObjectAPIName: "User"}}}},
	}
	specs := map[string]searchObjectSpec{
		"account":     {APIName: "Account", TableType: &standardObject},
		"contact":     {APIName: "Contact", TableType: &standardObject},
		"opportunity": {APIName: "Opportunity", TableType: &standardObject},
		"task":        {APIName: "Task", TableType: &standardObject},
		"user":        {APIName: "User", TableType: &standardObject},
	}
	selection, err := pageLayoutPermissionSelection(layout, specs)
	if err != nil {
		t.Fatal(err)
	}
	if selection == nil || len(selection.Sections) != 3 ||
		len(selection.ResourceTypes) != 5 ||
		selection.ResourceTypes[0] != "Account" ||
		selection.ResourceTypes[1] != "Contact" ||
		selection.ResourceTypes[2] != "Opportunity" ||
		selection.ResourceTypes[3] != "Task" ||
		selection.ResourceTypes[4] != "User" {
		t.Fatalf("selection = %#v", selection)
	}
}

func TestPageLayoutDottedFieldPathUsesTargetAuthorityAndFailsClosed(t *testing.T) {
	standardObject := "standard"
	specs := map[string]searchObjectSpec{
		"account": {
			APIName: "Account", TableType: &standardObject,
			Fields: []searchFieldSpec{{APIName: "OwnerId", Label: "Owner", ReferenceTo: []string{"User"}}},
		},
		"user": {
			APIName: "User", TableType: &standardObject,
			Fields: []searchFieldSpec{{APIName: "Name", Label: "Full Name"}, {APIName: "Secret", Label: "Secret"}},
		},
	}
	layout := appPageLayout{
		ObjectAPIName: "Account",
		Sections: []appPageLayoutSection{{
			Fields: []string{"OwnerId.Name", "OwnerId.Secret"},
			FieldLabels: map[string]string{
				"OwnerId.Name": "Account Owner", "OwnerId.Secret": "Hidden owner value",
			},
		}},
	}
	selection, err := pageLayoutPermissionSelection(layout, specs)
	if err != nil {
		t.Fatal(err)
	}
	if !slices.Equal(selection.ResourceTypes, []string{"Account", "User"}) {
		t.Fatalf("selection = %#v", selection)
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Account", CanRead: true}, {ResourceType: "User", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Account", FieldName: "OwnerId", CanRead: true},
			{ResourceType: "User", FieldName: "Name", CanRead: true},
		},
	}
	authorized, err := authorizePageLayout(layout, specs, permissions, testCurrentListFields(specs))
	if err != nil {
		t.Fatal(err)
	}
	if len(authorized.Sections) != 1 ||
		!slices.Equal(authorized.Sections[0].Fields, []string{"OwnerId.Name"}) ||
		len(authorized.Sections[0].FieldLabels) != 1 ||
		authorized.Sections[0].FieldLabels["OwnerId.Name"] != "Account Owner" {
		t.Fatalf("authorized dotted fields = %#v", authorized.Sections)
	}
}

func TestAuthorizePageLayoutFiltersRelationshipExpressionMetadataByFLS(t *testing.T) {
	standardObject := "standard"
	specs := map[string]searchObjectSpec{
		"account": {
			APIName: "Account", TableType: &standardObject,
			Fields: []searchFieldSpec{
				{APIName: "Name"},
				{APIName: "Parent__c", ReferenceTo: []string{"Parent__c"}},
				{APIName: "OwnerId", ReferenceTo: []string{"User"}},
				{APIName: "Quote__c", ReferenceTo: []string{"Quote"}},
			},
		},
		"parent__c": {
			APIName: "Parent__c", TableType: &standardObject,
			Fields: []searchFieldSpec{
				{APIName: "Public__c"}, {APIName: "Secret__c"},
				{APIName: "OwnerId", ReferenceTo: []string{"User"}},
			},
		},
		"user": {
			APIName: "User", TableType: &standardObject,
			Fields: []searchFieldSpec{{APIName: "Name"}, {APIName: "Secret"}},
		},
		"quote": {
			APIName: "Quote", TableType: &standardObject,
			Fields: []searchFieldSpec{{APIName: "HiddenTarget__c"}},
		},
	}
	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Account", CanRead: true, CanEdit: true},
			{ResourceType: "Parent__c", CanRead: true},
			{ResourceType: "User", CanRead: true},
			{ResourceType: "Quote", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Account", FieldName: "Name", CanRead: true},
			{ResourceType: "Account", FieldName: "Parent__c", CanRead: true},
			{ResourceType: "Account", FieldName: "OwnerId", CanRead: true},
			{ResourceType: "Account", FieldName: "Quote__c", CanRead: true},
			{ResourceType: "Parent__c", FieldName: "Public__c", CanRead: true},
			{ResourceType: "Parent__c", FieldName: "OwnerId", CanRead: true},
			{ResourceType: "User", FieldName: "Name", CanRead: true},
		},
	}
	layout := appPageLayout{
		ObjectAPIName: "Account",
		Sections: []appPageLayoutSection{
			{ID: "safe", Fields: []string{"Name"}, VisibilityCondition: "record.Parent__r.Public__c != null"},
			{ID: "hidden-condition", Fields: []string{"Name"}, VisibilityCondition: "record.Parent__r.Secret__c != null"},
			{ID: "hidden-config", Fields: []string{"Name"}, ComponentConfig: map[string]any{"when": "record.Owner.Secret != null"}},
			{ID: "hidden-label", Fields: []string{"Name"}, FieldLabels: map[string]string{"Name": "Quote.HiddenTarget__c"}},
		},
		RecordPanels: []appRecordPanel{
			{ID: "safe", VisibilityCondition: "record.Parent__r.Public__c != null"},
			{ID: "safe-multihop", VisibilityCondition: "record.Parent__r.Owner.Name != null"},
			{ID: "hidden", VisibilityCondition: "record.Parent__r.Secret__c != null"},
			{ID: "hidden-multihop", VisibilityCondition: "record.Parent__r.Owner.Secret != null"},
		},
		HeaderActions: []appLayoutAction{
			{Name: "Edit", Label: "Edit", Type: "Standard"},
			{Name: "hidden", Label: "Hidden", Type: "Standard", VisibilityCondition: "record.Owner.Secret != null"},
			{Name: "hidden-config", Label: "Hidden config", Type: "Standard", Config: map[string]any{"when": "record.Owner.Secret != null"}},
		},
		RecordStates: []appRecordStateRule{
			{ID: "safe", When: "record.Parent__r.Public__c != null"},
			{ID: "hidden-when", When: "record.Parent__r.Secret__c != null"},
			{ID: "hidden-banner", When: "record.Name != null", Banner: &appRecordStateBanner{Body: "record.Parent__r.Secret__c"}},
			{ID: "hidden-override", When: "record.Name != null", ActionOverrides: []appRecordStateActionOverride{{Reason: "record.Owner.Secret"}}},
			{ID: "hidden-readonly", When: "record.Name != null", ReadOnly: &appRecordStateReadOnly{Enabled: true, Reason: "Quote.HiddenTarget__c"}},
		},
	}

	selection, err := pageLayoutPermissionSelection(layout, specs)
	if err != nil {
		t.Fatal(err)
	}
	if !slices.Equal(selection.ResourceTypes, []string{"Account", "Parent__c", "Quote", "User"}) {
		t.Fatalf("selection = %#v", selection)
	}
	authorized, err := authorizePageLayout(layout, specs, permissions, testCurrentListFields(specs))
	if err != nil {
		t.Fatal(err)
	}
	if len(authorized.Sections) != 2 || authorized.Sections[0].ID != "safe" ||
		authorized.Sections[1].ID != "hidden-label" ||
		len(authorized.Sections[1].FieldLabels) != 0 {
		t.Fatalf("sections = %#v", authorized.Sections)
	}
	if len(authorized.RecordPanels) != 2 || authorized.RecordPanels[0].ID != "safe" ||
		authorized.RecordPanels[1].ID != "safe-multihop" ||
		len(authorized.HeaderActions) != 1 || authorized.HeaderActions[0].Name != "Edit" ||
		len(authorized.RecordStates) != 1 || authorized.RecordStates[0].ID != "safe" {
		t.Fatalf(
			"authorized presentation = %#v / %#v / %#v",
			authorized.RecordPanels, authorized.HeaderActions, authorized.RecordStates,
		)
	}
}

func TestPageLayoutAuthorityResolvesUserObjectQualifier(t *testing.T) {
	standardObject := "standard"
	specs := map[string]searchObjectSpec{
		"account": {
			APIName: "Account", TableType: &standardObject,
			Fields: []searchFieldSpec{{APIName: "Name"}},
		},
		"user": {
			APIName: "User", TableType: &standardObject,
			Fields: []searchFieldSpec{{APIName: "Name"}, {APIName: "Secret"}},
		},
	}
	layout := appPageLayout{
		ObjectAPIName: "Account",
		RecordPanels: []appRecordPanel{
			{ID: "safe-user-field", VisibilityCondition: "User.Name != null"},
			{ID: "hidden-user-field", VisibilityCondition: "user.Secret != null"},
		},
	}

	selection, err := pageLayoutPermissionSelection(layout, specs)
	if err != nil {
		t.Fatal(err)
	}
	if !slices.Equal(selection.ResourceTypes, []string{"Account", "User"}) {
		t.Fatalf("selection = %#v", selection)
	}

	permissions := permissionsemantic.Response{
		ObjectPermissions: []permissionsemantic.ObjectPermission{
			{ResourceType: "Account", CanRead: true},
			{ResourceType: "User", CanRead: true},
		},
		FieldPermissions: []permissionsemantic.FieldPermission{
			{ResourceType: "Account", FieldName: "Name", CanRead: true},
			{ResourceType: "User", FieldName: "Name", CanRead: true},
		},
	}
	authorized, err := authorizePageLayout(layout, specs, permissions, testCurrentListFields(specs))
	if err != nil {
		t.Fatal(err)
	}
	if len(authorized.RecordPanels) != 1 ||
		authorized.RecordPanels[0].ID != "safe-user-field" {
		t.Fatalf("record panels = %#v", authorized.RecordPanels)
	}
}

func TestPageLayoutPermissionSelectionFailsClosedOverContractLimit(t *testing.T) {
	standardObject := "standard"
	specs := make(map[string]searchObjectSpec, permissionsemantic.MaxSelectedResourceTypes+1)
	layout := appPageLayout{ObjectAPIName: "Object00"}
	for index := 0; index <= permissionsemantic.MaxSelectedResourceTypes; index++ {
		apiName := fmt.Sprintf("Object%02d", index)
		specs[strings.ToLower(apiName)] = searchObjectSpec{
			APIName: apiName, TableType: &standardObject,
		}
		if index > 0 {
			layout.RelatedLists = append(layout.RelatedLists, appRelatedList{ObjectAPIName: apiName})
		}
	}
	selection, err := pageLayoutPermissionSelection(layout, specs)
	if err == nil || selection != nil {
		t.Fatalf("selection = %#v, err = %v", selection, err)
	}
}

func TestPageLayoutFromViewAcceptsEffectiveOnlyTenantLayout(t *testing.T) {
	behavior := json.RawMessage(`{
		"objectApiName":"Credits__c",
		"layoutName":"Credits Layout",
		"isDefault":true,
		"tabs":["Details","Qualification"],
		"sections":[{"id":"qualification","label":"Qualification","tab":"Qualification","columns":2,"fields":["Name","Credit_Type__c"]}]
	}`)
	view := catalogsemantic.AppMetadataViewV3{
		EffectiveComponents: []catalogsemantic.EffectiveMetadataComponent{{
			ComponentKind:          catalogsemantic.EffectivePageLayoutComponentKind,
			ComponentFormatVersion: 1,
			BusinessKey:            "Credits__c/default",
			ContentDigest: catalogsemantic.ComputeEffectiveMetadataComponentDigest(
				behavior,
			),
			CanonicalBehavior: behavior,
		}},
	}
	layout, err := testPageLayoutFromView(view, "Credits__c")
	if err != nil {
		t.Fatal(err)
	}
	if layout.ObjectAPIName != "Credits__c" || layout.LayoutName != "Credits Layout" ||
		layout.ID != "Credits__c-default-layout" || len(layout.Sections) != 1 ||
		layout.Sections[0].Tab != "Qualification" || len(layout.Sections[0].Fields) != 2 {
		t.Fatalf("effective-only tenant layout = %#v", layout)
	}
}

func TestProjectRecordTypesCrossChecksAuthoredNamesAndEffectiveFacts(t *testing.T) {
	behavior := []byte(`{"recordTypes":[{"objectApiName":"Order","types":[{"name":"Renewal"}]}]}`)
	view := catalogsemantic.AppMetadataViewV3{AppComponents: []catalogsemantic.AppMetadataComponent{{
		ComponentKind: appRecordTypeComponentKind, ComponentFormatVersion: 1, CanonicalBehavior: behavior,
	}}}
	view = withTestEffectiveRecordTypes(t, view, "Order", effectiveRecordTypeDefinition{
		RecordTypeID: "rt_5bbdc1ed9dd9761c873c8ca8f24a073070c0352a",
		Name:         "Renewal", DeveloperName: "Renewal",
	})
	permissions := permissionsemantic.Response{RecordTypePermissions: []permissionsemantic.RecordTypePermission{
		testRecordTypePermission("Order", "rt_5bbdc1ed9dd9761c873c8ca8f24a073070c0352a", true, true),
	}}
	projection, err := projectRecordTypes(view, permissions, "Order")
	if err != nil || !projection.hasConcreteRecordTypes || projection.defaultRecordTypeID != "rt_5bbdc1ed9dd9761c873c8ca8f24a073070c0352a" ||
		projection.availableConcreteCount != 1 || len(projection.entries) != 1 {
		t.Fatalf("Order projection = %#v, %v", projection, err)
	}
	if _, found := projection.entry(masterRecordTypeID); found {
		t.Fatal("concrete RecordType projection retained synthetic Master")
	}
	lead, err := projectRecordTypes(view, permissionsemantic.Response{}, "Lead")
	if err != nil || lead.hasConcreteRecordTypes || lead.defaultRecordTypeID != masterRecordTypeID {
		t.Fatalf("Lead Master projection = %#v, %v", lead, err)
	}
	for name, mutate := range map[string]func(*permissionsemantic.Response){
		"missing concrete": func(response *permissionsemantic.Response) {
			response.RecordTypePermissions = nil
		},
		"hidden default": func(response *permissionsemantic.Response) {
			response.RecordTypePermissions[0].Available = false
		},
		"unknown concrete": func(response *permissionsemantic.Response) {
			response.RecordTypePermissions = append(
				response.RecordTypePermissions,
				testRecordTypePermission("Order", "rt-unknown", true, false),
			)
		},
		"runtime master": func(response *permissionsemantic.Response) {
			response.RecordTypePermissions = append(
				response.RecordTypePermissions,
				testRecordTypePermission("Order", masterRecordTypeID, true, false),
			)
		},
	} {
		t.Run(name, func(t *testing.T) {
			candidate := permissions
			candidate.RecordTypePermissions = append([]permissionsemantic.RecordTypePermission(nil), permissions.RecordTypePermissions...)
			mutate(&candidate)
			_, err := projectRecordTypes(view, candidate, "Order")
			if err == nil {
				t.Fatalf("error = %v, want unavailable", err)
			}
		})
	}
	mismatchedView := catalogsemantic.AppMetadataViewV3{AppComponents: view.AppComponents}
	mismatchedView = withTestEffectiveRecordTypes(t, mismatchedView, "Order", effectiveRecordTypeDefinition{
		RecordTypeID: "rt_5bbdc1ed9dd9761c873c8ca8f24a073070c0352a",
		Name:         "Unknown", DeveloperName: "Unknown",
	})
	if _, err := projectRecordTypes(mismatchedView, permissions, "Order"); err == nil {
		t.Fatal("authored/effective RecordType mismatch was accepted")
	}
}

func TestProjectRecordTypesRejectsDuplicateEffectiveDefinitions(t *testing.T) {
	view := withTestEffectiveRecordTypes(
		t, catalogsemantic.AppMetadataViewV3{}, "Order",
		effectiveRecordTypeDefinition{RecordTypeID: "rt-one", Name: "One", DeveloperName: "Same"},
		effectiveRecordTypeDefinition{RecordTypeID: "rt-two", Name: "Two", DeveloperName: "same"},
	)
	permissions := permissionsemantic.Response{RecordTypePermissions: []permissionsemantic.RecordTypePermission{
		testRecordTypePermission("Order", "rt-one", true, true),
		testRecordTypePermission("Order", "rt-two", true, false),
	}}
	if _, err := projectRecordTypes(view, permissions, "Order"); err == nil {
		t.Fatal("duplicate effective RecordType definition was accepted")
	}
}

func TestProjectRecordTypesAllowsAdditionalCatalogTypesBeyondL1AuthoredSubset(t *testing.T) {
	behavior := []byte(`{"recordTypes":[{"objectApiName":"Quote","types":[{"name":"Standard"}]}]}`)
	view := catalogsemantic.AppMetadataViewV3{AppComponents: []catalogsemantic.AppMetadataComponent{{
		ComponentKind: appRecordTypeComponentKind, ComponentFormatVersion: 1, CanonicalBehavior: behavior,
	}}}
	for _, test := range []struct {
		object       string
		authoredName string
	}{
		{object: "Account"},
		{object: "Opportunity"},
		{object: "Quote", authoredName: "Standard"},
	} {
		t.Run(test.object, func(t *testing.T) {
			candidateView := view
			definitions := make([]effectiveRecordTypeDefinition, 0, 2)
			permissions := permissionsemantic.Response{}
			if test.authoredName != "" {
				definitions = append(definitions, effectiveRecordTypeDefinition{
					RecordTypeID: "rt_l1_standard", Name: test.authoredName,
					DeveloperName: test.authoredName,
				})
				permissions.RecordTypePermissions = append(
					permissions.RecordTypePermissions,
					testRecordTypePermission(test.object, "rt_l1_standard", true, false),
				)
			}
			definitions = append(definitions, effectiveRecordTypeDefinition{
				RecordTypeID: "rt_tenant_extension", Name: "Tenant Extension",
				DeveloperName: "TenantExtension",
			})
			candidateView = withTestEffectiveRecordTypes(t, candidateView, test.object, definitions...)
			permissions.RecordTypePermissions = append(
				permissions.RecordTypePermissions,
				testRecordTypePermission(test.object, "rt_tenant_extension", true, true),
			)

			projection, err := projectRecordTypes(candidateView, permissions, test.object)
			if err != nil {
				t.Fatal(err)
			}
			if !projection.hasConcreteRecordTypes || projection.defaultRecordTypeID != "rt_tenant_extension" ||
				projection.availableConcreteCount != len(permissions.RecordTypePermissions) ||
				len(projection.entries) != len(permissions.RecordTypePermissions) {
				t.Fatalf("projection = %#v, permissions = %#v", projection, permissions.RecordTypePermissions)
			}
			if _, found := projection.entry(masterRecordTypeID); found {
				t.Fatal("tenant-extended concrete projection retained synthetic Master")
			}
			if entry, found := projection.entry("rt_tenant_extension"); !found || entry.Name != "Tenant Extension" {
				t.Fatalf("tenant extension = %#v, found=%v", entry, found)
			}
		})
	}
}

func TestAuthorizePageLayoutOmitsUnauthorizedRelatedList(t *testing.T) {
	tableType := "standard"
	specs := map[string]searchObjectSpec{
		"account": {APIName: "Account", TableType: &tableType},
		"contact": {
			APIName: "Contact", TableType: &tableType,
			Fields: []searchFieldSpec{{APIName: "AccountId"}, {APIName: "Secret"}},
		},
	}
	layout := appPageLayout{ObjectAPIName: "Account", RelatedLists: []appRelatedList{{
		ObjectAPIName: "Contact", LookupField: "AccountId", Fields: []string{"Secret"},
	}}}
	for name, permissions := range map[string]permissionsemantic.Response{
		"child object hidden": {
			ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Account", CanRead: true}},
		},
		"lookup hidden": {
			ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Account", CanRead: true}, {ResourceType: "Contact", CanRead: true}},
		},
		"all authored fields hidden": {
			ObjectPermissions: []permissionsemantic.ObjectPermission{{ResourceType: "Account", CanRead: true}, {ResourceType: "Contact", CanRead: true}},
			FieldPermissions:  []permissionsemantic.FieldPermission{{ResourceType: "Contact", FieldName: "AccountId", CanRead: true}},
		},
	} {
		t.Run(name, func(t *testing.T) {
			authorized, err := authorizePageLayout(layout, specs, permissions, testCurrentListFields(specs))
			if err != nil {
				t.Fatal(err)
			}
			if len(authorized.RelatedLists) != 0 {
				t.Fatalf("related lists = %#v, want none", authorized.RelatedLists)
			}
		})
	}
}
