package actionvalidate

import (
	"strings"
	"testing"
)

// TestStrictChildCopyInConfig pins the shared primitive. The same
// function is called by pack-apply paths (platform_ux,
// tenants/pingcap/actions) AND the admin/API writer path
// (MetadataService.CreateAction / UpdateAction). All three writers
// must reject the same shape — any non-empty workflow.childCopy — or
// the Phase 2 single-runtime contract breaks.
//
// Phase 2b contract: workflow.childCopy is a retired surface. The
// runtime doesn't read it. Accepting new authoring here would make
// this a fake metadata surface (writers/validators/runtime saying
// different things). Legacy residue flows through overlay deep-merge
// untouched because overlays submit only their ConfigOverlay delta;
// this primitive runs on that delta, not on the merged result.
func TestStrictChildCopyInConfig(t *testing.T) {
	cases := []struct {
		name       string
		config     map[string]interface{}
		wantErrMsg string
	}{
		{
			name:   "nil config passes",
			config: nil,
		},
		{
			name:   "no workflow key passes",
			config: map[string]interface{}{"lookupField": "AccountId"},
		},
		{
			name: "workflow without childCopy passes",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{"fieldMappings": map[string]interface{}{"Name": "Name"}},
			},
		},
		{
			name: "workflow with empty childCopy array passes (explicit clear lever)",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{
					"childCopy": []interface{}{},
				},
			},
		},
		{
			name: "explicit null passes (clear lever)",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{
					"childCopy": nil,
				},
			},
		},
		{
			name: "string value REJECTED — any authored content is a fake surface",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{
					"childCopy": "not-an-array",
				},
			},
			wantErrMsg: `string "not-an-array"`,
		},
		{
			name: "object value REJECTED",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{
					"childCopy": map[string]interface{}{"sourceObject": "X"},
				},
			},
			wantErrMsg: "object with 1 key",
		},
		{
			name: "empty object value REJECTED — presence is the violation, not size",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{
					"childCopy": map[string]interface{}{},
				},
			},
			wantErrMsg: "object with 0 key",
		},
		{
			name: "number value REJECTED",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{
					"childCopy": float64(42),
				},
			},
			wantErrMsg: "number 42",
		},
		{
			name: "bool value REJECTED",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{
					"childCopy": true,
				},
			},
			wantErrMsg: "bool true",
		},
		{
			name: "clean childCopy entry REJECTED — surface is retired",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{
					"childCopy": []interface{}{
						map[string]interface{}{
							"sourceObject":      "OpportunityLineItem",
							"sourceLookupField": "OpportunityId",
							"targetObject":      "quote_product__c",
							"targetLookupField": "Quote__c",
						},
					},
				},
			},
			wantErrMsg: "retired",
		},
		{
			name: "childCopy entry with forbidden sub-keys REJECTED (broader check subsumes the old narrow one)",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{
					"childCopy": []interface{}{
						map[string]interface{}{
							"sourceIdField":     "Quote_Product__c",
							"sourceObject":      "quote_product__c",
							"sourceLookupField": "Quote__c",
							"targetObject":      "order_product__c",
							"targetLookupField": "Order__c",
						},
					},
				},
			},
			wantErrMsg: "retired",
		},
		{
			name: "multiple entries REJECTED — count surfaces in the error",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{
					"childCopy": []interface{}{
						map[string]interface{}{"sourceObject": "A", "targetObject": "B"},
						map[string]interface{}{"sourceObject": "C", "targetObject": "D"},
					},
				},
			},
			wantErrMsg: "2 entrie",
		},
		{
			name: "rejection names the replacement surface",
			config: map[string]interface{}{
				"workflow": map[string]interface{}{
					"childCopy": []interface{}{
						map[string]interface{}{"sourceObject": "X"},
					},
				},
			},
			wantErrMsg: "_System_Object.ChildCloneRules",
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			err := StrictChildCopyInConfig("TestAction", tc.config)
			if tc.wantErrMsg == "" {
				if err != nil {
					t.Fatalf("expected nil error, got %v", err)
				}
				return
			}
			if err == nil {
				t.Fatalf("expected error containing %q, got nil", tc.wantErrMsg)
			}
			if !strings.Contains(err.Error(), tc.wantErrMsg) {
				t.Fatalf("error %q does not contain %q", err.Error(), tc.wantErrMsg)
			}
		})
	}
}
