package schemautil

import (
	"testing"

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

// allowAllParents is the test stand-in for "this caller can read every virtual
// compound parent" — production callers pass a closure backed by
// permissions.CheckFieldVisibilityWithUser.
func allowAllParents(string) bool { return true }

// userLikeSchema mirrors the effective User schema after the compound-virtual
// decorator runs: a physical-less Name parent (IsVirtual + VirtualKind compound)
// composed from FirstName + LastName.
func userLikeSchema() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName: "User",
		Fields: []models.FieldMetadata{
			{APIName: "FirstName"},
			{APIName: "LastName"},
			{
				APIName:      "Name",
				IsVirtual:    true,
				VirtualKind:  constants.VirtualFieldCompound,
				IsCompound:   true,
				ComposedFrom: []string{"FirstName", "LastName"},
			},
		},
	}
}

func TestSynthesizeVirtualCompoundFields_SynthesizesAbsentParent(t *testing.T) {
	schema := userLikeSchema()
	record := models.SObject{"FirstName": "System", "LastName": "Administrator"}

	SynthesizeVirtualCompoundFields(schema, record, allowAllParents)

	if got, want := record["Name"], "System Administrator"; got != want {
		t.Fatalf("Name = %#v, want %q", got, want)
	}
}

func TestSynthesizeVirtualCompoundFields_OverwritesNilParent(t *testing.T) {
	// record-ui / GetRecord surface the parent key with a nil value because the
	// field exists in the schema but has no physical column. Issue #976.
	schema := userLikeSchema()
	record := models.SObject{"FirstName": "Ada", "LastName": "Lovelace", "Name": nil}

	SynthesizeVirtualCompoundFields(schema, record, allowAllParents)

	if got, want := record["Name"], "Ada Lovelace"; got != want {
		t.Fatalf("Name = %#v, want %q", got, want)
	}
}

func TestSynthesizeVirtualCompoundFields_PreservesNonBlankParent(t *testing.T) {
	// Autonumber-rendered or otherwise pre-populated compound parents keep their
	// value (parity with the analytics projection guard).
	schema := userLikeSchema()
	record := models.SObject{"FirstName": "Ada", "LastName": "Lovelace", "Name": "Pre Rendered"}

	SynthesizeVirtualCompoundFields(schema, record, allowAllParents)

	if got, want := record["Name"], "Pre Rendered"; got != want {
		t.Fatalf("Name = %#v, want %q", got, want)
	}
}

func TestSynthesizeVirtualCompoundFields_AllBlankMembersLeaveParentAbsent(t *testing.T) {
	schema := userLikeSchema()
	record := models.SObject{"FirstName": "", "LastName": ""}

	SynthesizeVirtualCompoundFields(schema, record, allowAllParents)

	if v, ok := record["Name"]; ok {
		t.Fatalf("Name should stay absent for fully blank members, got %#v", v)
	}
}

func TestSynthesizeVirtualCompoundFields_IgnoresNonVirtualCompoundParent(t *testing.T) {
	// Only IsVirtual + VirtualKindCompound parents are synthesized; a regular
	// compound parent with a physical column is left to the persistence layer.
	schema := &models.ObjectMetadata{
		APIName: "Quote",
		Fields: []models.FieldMetadata{
			{APIName: "QuoteNumber"},
			{APIName: "Name", IsCompound: true, ComposedFrom: []string{"QuoteNumber"}},
		},
	}
	record := models.SObject{"QuoteNumber": "00000001"}

	SynthesizeVirtualCompoundFields(schema, record, allowAllParents)

	if v, ok := record["Name"]; ok {
		t.Fatalf("non-virtual compound parent should not be synthesized, got %#v", v)
	}
}

func TestSynthesizeVirtualCompoundFields_SkipsParentDeniedByFLS(t *testing.T) {
	// Regression for the FLS leak (PR #1641 gate review): when the caller's
	// profile denies the virtual parent (FieldPermissions(User.Name)=false) but
	// can read its members, synthesis must NOT reintroduce the parent after the
	// persistence layer's FLS filter — matching the analytics path, which only
	// projects FLS-visible requested fields.
	schema := userLikeSchema()
	record := models.SObject{"FirstName": "Ada", "LastName": "Lovelace"}

	denyName := func(parentAPIName string) bool { return parentAPIName != "Name" }
	SynthesizeVirtualCompoundFields(schema, record, denyName)

	if v, ok := record["Name"]; ok {
		t.Fatalf("Name must stay absent when FLS denies the parent, got %#v", v)
	}
}

func TestSynthesizeVirtualCompoundFields_SynthesizesParentAllowedByFLS(t *testing.T) {
	// Complement of the deny case: a profile that can read the parent and every
	// component gets it synthesized, so issue #976 (lookup labels) stays fixed
	// for permitted profiles.
	schema := userLikeSchema()
	record := models.SObject{"FirstName": "Ada", "LastName": "Lovelace"}

	allow := func(f string) bool { return f == "Name" || f == "FirstName" || f == "LastName" }
	SynthesizeVirtualCompoundFields(schema, record, allow)

	if got, want := record["Name"], "Ada Lovelace"; got != want {
		t.Fatalf("Name = %#v, want %q", got, want)
	}
}

func TestSynthesizeVirtualCompoundFields_SkipsWhenComponentDeniedByFLS(t *testing.T) {
	// Regression for Ian's P1 (PR #1641 final review): the parent is readable but
	// a defining component (FirstName) is denied. Synthesis must fail closed —
	// never build a partial Name out of the remaining visible component, which
	// would leak a value derived from a field the caller cannot read.
	schema := userLikeSchema()
	record := models.SObject{"FirstName": "Ada", "LastName": "Lovelace"}

	denyFirstName := func(f string) bool { return f != "FirstName" }
	SynthesizeVirtualCompoundFields(schema, record, denyFirstName)

	if v, ok := record["Name"]; ok {
		t.Fatalf("Name must stay absent when a component is FLS-denied, got %#v", v)
	}
}

func TestSynthesizeVirtualCompoundFields_FailsClosedWhenComponentKeyMissing(t *testing.T) {
	// Post-FLS, a denied component is dropped entirely from the filtered record.
	// Even if the visibility predicate would allow it, a missing component key
	// means we cannot prove read access for the value that produced the record —
	// fail closed rather than synthesize a partial parent.
	schema := userLikeSchema()
	record := models.SObject{"LastName": "Lovelace"} // FirstName key absent

	SynthesizeVirtualCompoundFields(schema, record, allowAllParents)

	if v, ok := record["Name"]; ok {
		t.Fatalf("Name must stay absent when a component key is missing post-FLS, got %#v", v)
	}
}

func TestSynthesizeVirtualCompoundFields_SynthesizesWhenComponentPresentButBlank(t *testing.T) {
	// Ian's explicitly-allowed case: every component is readable and present, but
	// one is blank. The "only LastName populated" record still synthesizes — a
	// present nil/blank component is fine, only a MISSING key fails closed.
	schema := userLikeSchema()
	record := models.SObject{"FirstName": "", "LastName": "Lovelace"}

	SynthesizeVirtualCompoundFields(schema, record, allowAllParents)

	if got, want := record["Name"], "Lovelace"; got != want {
		t.Fatalf("Name = %#v, want %q", got, want)
	}
}
