package tenantoverlay

import (
	"testing"

	domainschema "github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

func TestBuildTenantColumnDefsGeneratePolymorphicTypeColumns(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	defs := buildTenantColumnDefs([]packs.ObjectSpec{{
		APIName:   "Issue__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName:       "RelatedTo__c",
			Label:         "Related To",
			Type:          "reference",
			ReferenceTo:   []string{"Account", "Opportunity"},
			IsPolymorphic: true,
		}},
	}}, sm)

	if len(defs) != 1 {
		t.Fatalf("defs len = %d, want 1", len(defs))
	}
	if !tenantColumnExists(defs[0].Columns, "RelatedTo__c_type") {
		t.Fatalf("generated RelatedTo__c_type column missing: %#v", defs[0].Columns)
	}
}

func TestBuildTenantColumnDefsInjectRecordTypeIDOnlyForCustomObjects(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	specs := []packs.ObjectSpec{
		{
			APIName:   "Account",
			TableType: string(constants.TableTypeStandardObject),
			Category:  "tenant",
			Fields: []packs.FieldSpec{{
				APIName: "Score__c",
				Label:   "Score",
				Type:    "double",
			}},
		},
		{
			APIName:   "Issue__c",
			TableType: string(constants.TableTypeCustomObject),
			Category:  "tenant",
			Fields: []packs.FieldSpec{{
				APIName:     "Name",
				Label:       "Name",
				Type:        "string",
				Required:    true,
				IsNameField: true,
			}},
		},
	}
	defs := buildTenantColumnDefs(specs, sm)

	if len(defs) != 2 {
		t.Fatalf("defs len = %d, want 2", len(defs))
	}
	if tenantColumnExists(defs[0].Columns, constants.FieldRecordTypeID) {
		t.Fatalf("standard-object overlay unexpectedly includes %s: %#v", constants.FieldRecordTypeID, defs[0].Columns)
	}
	if !tenantColumnExists(defs[1].Columns, constants.FieldRecordTypeID) {
		t.Fatalf("custom-object overlay missing %s: %#v", constants.FieldRecordTypeID, defs[1].Columns)
	}
}

func TestBuildTenantRequiredBusinessFieldStaysPhysicallyNullable(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	specs := []packs.ObjectSpec{{
		APIName:   "Issue__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName:  "RequiredText__c",
			Label:    "Required Text",
			Type:     "string",
			Required: true,
		}},
	}}

	defs := buildTenantColumnDefs(specs, sm)
	if len(defs) != 1 {
		t.Fatalf("defs len = %d, want 1", len(defs))
	}
	col := tenantColumnByName(t, defs[0].Columns, "RequiredText__c")
	if !col.Nullable {
		t.Fatalf("tenant column Nullable = false, want true; tenant required fields are save-time validation only")
	}

	fields := buildTenantFieldMetadata(specs, sm)
	var found *metadata.FieldWithContext
	for i := range fields {
		if fields[i].TableName == "Issue__c" && fields[i].Field != nil && fields[i].Field.APIName == "RequiredText__c" {
			found = &fields[i]
			break
		}
	}
	if found == nil {
		t.Fatal("RequiredText__c FieldWithContext not found")
	}
	if !found.Field.Required {
		t.Fatalf("Field.Required = false, want true")
	}
	if !found.ColumnDefinition.Nullable {
		t.Fatalf("FieldWithContext.ColumnDefinition.Nullable = false, want true")
	}
}

func TestBuildTenantUniqueFieldPropagatesToColumnAndMetadata(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	specs := []packs.ObjectSpec{{
		APIName:   "Account",
		TableType: string(constants.TableTypeStandardObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName: "TenantExternalID__c",
			Label:   "Tenant External ID",
			Type:    "string",
			Unique:  true,
		}},
	}}

	defs := buildTenantColumnDefs(specs, sm)
	if len(defs) != 1 {
		t.Fatalf("defs len = %d, want 1", len(defs))
	}
	col := tenantColumnByName(t, defs[0].Columns, "TenantExternalID__c")
	if !col.Unique {
		t.Fatal("tenant ColumnDefinition.Unique = false, want true")
	}

	fields := buildTenantFieldMetadata(specs, sm)
	for _, field := range fields {
		if field.TableName != "Account" || field.Field == nil || field.Field.APIName != "TenantExternalID__c" {
			continue
		}
		if !field.Field.IsUnique {
			t.Fatal("tenant FieldMetadata.IsUnique = false, want true")
		}
		if !field.ColumnDefinition.Unique {
			t.Fatal("tenant FieldWithContext.ColumnDefinition.Unique = false, want true")
		}
		return
	}
	t.Fatal("TenantExternalID__c FieldWithContext not found")
}

func TestBuildTenantFieldMetadataRegistersGeneratedPolymorphicDiscriminatorReadOnly(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	specs := []packs.ObjectSpec{{
		APIName:   "Issue__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName:       "RelatedTo__c",
			Label:         "Related To",
			Type:          "reference",
			ReferenceTo:   []string{"Account", "Opportunity"},
			IsPolymorphic: true,
		}},
	}}
	fields := buildTenantFieldMetadata(specs, sm)

	for _, field := range fields {
		if field.Field == nil || field.Field.APIName != "RelatedTo__c_type" {
			continue
		}
		if field.Field.Createable || field.Field.Updateable {
			t.Fatalf("RelatedTo__c_type Createable/Updateable = %v/%v, want false/false", field.Field.Createable, field.Field.Updateable)
		}
		if field.OwnerKind != constants.FieldOwnerKindAdmin {
			t.Fatalf("RelatedTo__c_type OwnerKind = %q, want %q", field.OwnerKind, constants.FieldOwnerKindAdmin)
		}
		return
	}
	t.Fatal("generated RelatedTo__c_type field metadata not found")
}

func TestBuildTenantFieldMetadataInjectRecordTypeIDOnlyForCustomObjects(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	specs := []packs.ObjectSpec{
		{
			APIName:   "Account",
			TableType: string(constants.TableTypeStandardObject),
			Category:  "tenant",
			Fields: []packs.FieldSpec{{
				APIName: "Score__c",
				Label:   "Score",
				Type:    "double",
			}},
		},
		{
			APIName:   "Issue__c",
			TableType: string(constants.TableTypeCustomObject),
			Category:  "tenant",
			Fields: []packs.FieldSpec{{
				APIName:     "Name",
				Label:       "Name",
				Type:        "string",
				Required:    true,
				IsNameField: true,
			}},
		},
	}
	fields := buildTenantFieldMetadata(specs, sm)

	var accountHasRecordTypeID bool
	var issueHasRecordTypeID bool
	for _, field := range fields {
		if field.Field == nil || field.Field.APIName != constants.FieldRecordTypeID {
			continue
		}
		switch field.TableName {
		case "Account":
			accountHasRecordTypeID = true
		case "Issue__c":
			issueHasRecordTypeID = true
		}
	}

	if accountHasRecordTypeID {
		t.Fatalf("standard-object overlay unexpectedly includes %s field metadata", constants.FieldRecordTypeID)
	}
	if !issueHasRecordTypeID {
		t.Fatalf("custom-object overlay missing %s field metadata", constants.FieldRecordTypeID)
	}
}

func TestBuildTenantFieldMetadataLocksExplicitPolymorphicDiscriminatorDeclaration(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	fields := buildTenantFieldMetadata([]packs.ObjectSpec{{
		APIName:   "Issue__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{
			{
				APIName:       "RelatedTo__c",
				Label:         "Related To",
				Type:          "reference",
				ReferenceTo:   []string{"Account", "Opportunity"},
				IsPolymorphic: true,
			},
			{
				APIName: "RelatedTo__c_type",
				Label:   "Related To Type",
				Type:    "string",
			},
		},
	}}, sm)

	var matches int
	for _, field := range fields {
		if field.Field == nil || field.Field.APIName != "RelatedTo__c_type" {
			continue
		}
		matches++
		if field.Field.GeneratedForPolymorphicReference != "RelatedTo__c" {
			t.Fatalf("RelatedTo__c_type GeneratedForPolymorphicReference = %q, want RelatedTo__c", field.Field.GeneratedForPolymorphicReference)
		}
		if field.Field.Createable || field.Field.Updateable {
			t.Fatalf("explicit RelatedTo__c_type Createable/Updateable = %v/%v, want false/false", field.Field.Createable, field.Field.Updateable)
		}
	}
	if matches != 1 {
		t.Fatalf("RelatedTo__c_type metadata rows = %d, want 1", matches)
	}
}

// TestBuildTenantColumnDefsResolverLookupMultiTargetInfersPolymorphic proves
// that a Type=lookup multi-target overlay field resolves through
// ResolveFieldRelationshipSemantics(LifecycleAuthoredWrite) and gets
// IsPolymorphic=true without the pack having to set IsPolymorphic=true
// explicitly. This is the resolver-cutover parity test: before the cutover,
// the inline derivation only matched Type=reference, so Type=lookup
// multi-target fields were silently skipped.
func TestBuildTenantColumnDefsResolverLookupMultiTargetInfersPolymorphic(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	defs := buildTenantColumnDefs([]packs.ObjectSpec{{
		APIName:   "Issue__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName:     "RelatedTo__c",
			Label:       "Related To",
			Type:        "lookup",
			ReferenceTo: []string{"Account", "Contact"},
			// IsPolymorphic intentionally omitted — resolver must infer.
		}},
	}}, sm)

	if len(defs) != 1 {
		t.Fatalf("defs len = %d, want 1", len(defs))
	}
	// Find the RelatedTo__c column.
	var found bool
	for _, col := range defs[0].Columns {
		if col.Name == "RelatedTo__c" {
			found = true
			if !col.IsPolymorphic {
				t.Fatalf("RelatedTo__c IsPolymorphic = false, want true (resolver must infer for Type=lookup multi-target)")
			}
			if col.LogicalType != string(constants.FieldTypeReference) {
				t.Fatalf("RelatedTo__c LogicalType = %q, want %q (resolver must normalize lookup→reference)", col.LogicalType, constants.FieldTypeReference)
			}
		}
	}
	if !found {
		t.Fatalf("RelatedTo__c column not found (resolver should normalize lookup→reference so SQL type resolves): %#v", defs[0].Columns)
	}
	// Discriminator companion must exist.
	if !tenantColumnExists(defs[0].Columns, "RelatedTo__c_type") {
		t.Fatalf("generated RelatedTo__c_type column missing for Type=lookup multi-target: %#v", defs[0].Columns)
	}
}

// TestBuildTenantColumnDefsResolverNormalizesLookupToReference proves that
// a single-target Type=lookup field gets LogicalType normalized to
// "reference" so SQL type resolution succeeds.
func TestBuildTenantColumnDefsResolverNormalizesLookupToReference(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	defs := buildTenantColumnDefs([]packs.ObjectSpec{{
		APIName:   "Issue__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName:     "Account__c",
			Label:       "Account",
			Type:        "lookup",
			ReferenceTo: []string{"Account"},
		}},
	}}, sm)

	if len(defs) != 1 {
		t.Fatalf("defs len = %d, want 1", len(defs))
	}
	var found bool
	for _, col := range defs[0].Columns {
		if col.Name == "Account__c" {
			found = true
			if col.LogicalType != string(constants.FieldTypeReference) {
				t.Fatalf("Account__c LogicalType = %q, want %q", col.LogicalType, constants.FieldTypeReference)
			}
			if col.IsPolymorphic {
				t.Fatal("single-target lookup should not be polymorphic")
			}
		}
	}
	if !found {
		t.Fatalf("Account__c column not found (lookup should normalize to reference for SQL type resolution): %#v", defs[0].Columns)
	}
}

// TestBuildTenantFieldMetadataResolverLookupMultiTargetCreatesDiscriminator
// proves that buildTenantFieldMetadata resolves Type=lookup multi-target
// through the resolver and generates the discriminator companion field.
func TestBuildTenantFieldMetadataResolverLookupMultiTargetCreatesDiscriminator(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	specs := []packs.ObjectSpec{{
		APIName:   "Issue__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName:     "RelatedTo__c",
			Label:       "Related To",
			Type:        "lookup",
			ReferenceTo: []string{"Account", "Contact"},
			// IsPolymorphic intentionally omitted — resolver must infer.
		}},
	}}
	fields := buildTenantFieldMetadata(specs, sm)

	// Find the reference field.
	var refFieldFound bool
	for _, f := range fields {
		if f.Field == nil || f.Field.APIName != "RelatedTo__c" {
			continue
		}
		refFieldFound = true
		if !f.Field.IsPolymorphic {
			t.Fatal("RelatedTo__c FieldMetadata.IsPolymorphic = false, want true (resolver must infer for Type=lookup multi-target)")
		}
		if f.Field.Type != constants.FieldTypeReference {
			t.Fatalf("RelatedTo__c FieldMetadata.Type = %q, want %q (must be canonical)", f.Field.Type, constants.FieldTypeReference)
		}
	}
	if !refFieldFound {
		t.Fatal("RelatedTo__c field metadata not found (resolver should normalize lookup→reference)")
	}

	// Find the discriminator companion.
	var discriminatorFound bool
	for _, f := range fields {
		if f.Field == nil || f.Field.APIName != "RelatedTo__c_type" {
			continue
		}
		discriminatorFound = true
		if f.Field.GeneratedForPolymorphicReference != "RelatedTo__c" {
			t.Fatalf("RelatedTo__c_type GeneratedForPolymorphicReference = %q, want RelatedTo__c", f.Field.GeneratedForPolymorphicReference)
		}
		if f.Field.Createable || f.Field.Updateable {
			t.Fatalf("RelatedTo__c_type Createable/Updateable = %v/%v, want false/false", f.Field.Createable, f.Field.Updateable)
		}
	}
	if !discriminatorFound {
		t.Fatal("RelatedTo__c_type discriminator companion not found for Type=lookup multi-target")
	}
}

// TestBuildTenantFieldMetadataResolverLookupSingleTargetNoDiscriminator proves
// that a single-target Type=lookup field does NOT get a discriminator
// companion and is not polymorphic.
func TestBuildTenantFieldMetadataResolverLookupSingleTargetNoDiscriminator(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	specs := []packs.ObjectSpec{{
		APIName:   "Issue__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName:     "Account__c",
			Label:       "Account",
			Type:        "lookup",
			ReferenceTo: []string{"Account"},
		}},
	}}
	fields := buildTenantFieldMetadata(specs, sm)

	for _, f := range fields {
		if f.Field == nil {
			continue
		}
		if f.Field.APIName == "Account__c" {
			if f.Field.IsPolymorphic {
				t.Fatal("single-target lookup should not be polymorphic")
			}
			if f.Field.Type != constants.FieldTypeReference {
				t.Fatalf("Account__c Type = %q, want %q", f.Field.Type, constants.FieldTypeReference)
			}
		}
		if f.Field.APIName == "Account__c_type" {
			t.Fatal("single-target lookup should not generate discriminator companion")
		}
	}
}

func tenantColumnByName(t *testing.T, columns []domainschema.ColumnDefinition, name string) domainschema.ColumnDefinition {
	t.Helper()
	for _, col := range columns {
		if col.Name == name {
			return col
		}
	}
	t.Fatalf("column %s not found: %#v", name, columns)
	return domainschema.ColumnDefinition{}
}

func tenantColumnExists(columns []domainschema.ColumnDefinition, name string) bool {
	for _, col := range columns {
		if col.Name == name {
			return true
		}
	}
	return false
}

// TestBuildTenantColumnDefs_LookupAliasMultiTargetInfersPolymorphic proves
// that a tenant-overlay field authored with type "lookup" (the authoring
// alias for reference) and multiple ReferenceTo targets is resolved through
// the single SSoT resolver. The column definition must carry
// IsPolymorphic=true, canonical LogicalType="reference", and the
// discriminator companion column must be generated. Without resolver
// routing, "lookup" hits INVALID_TYPE_ERROR in MapFieldTypeToSQL and the
// inline polymorphic derivation only accepts literal "reference".
func TestBuildTenantColumnDefs_LookupAliasMultiTargetInfersPolymorphic(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	defs := buildTenantColumnDefs([]packs.ObjectSpec{{
		APIName:   "Ticket__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName:     "LinkedTo__c",
			Label:       "Linked To",
			Type:        "lookup",
			ReferenceTo: []string{"Account", "Contact"},
		}},
	}}, sm)

	if len(defs) != 1 {
		t.Fatalf("defs len = %d, want 1", len(defs))
	}
	var parentCol *domainschema.ColumnDefinition
	for i := range defs[0].Columns {
		if defs[0].Columns[i].Name == "LinkedTo__c" {
			parentCol = &defs[0].Columns[i]
			break
		}
	}
	if parentCol == nil {
		t.Fatalf("LinkedTo__c column not found in defs: %#v", defs[0].Columns)
	}
	if !parentCol.IsPolymorphic {
		t.Fatalf("LinkedTo__c IsPolymorphic = false, want true (lookup alias multi-target must infer polymorphic ownership)")
	}
	if parentCol.LogicalType != string(constants.FieldTypeReference) {
		t.Fatalf("LinkedTo__c LogicalType = %q, want %q (canonical type must be stamped for persisted reload safety)", parentCol.LogicalType, constants.FieldTypeReference)
	}
	if !tenantColumnExists(defs[0].Columns, "LinkedTo__c_type") {
		t.Fatalf("discriminator companion LinkedTo__c_type column missing: %#v", defs[0].Columns)
	}
}

// TestBuildTenantFieldMetadata_LookupAliasMultiTargetPersistsCanonicalShape
// proves that a tenant-overlay field authored with type "lookup" and
// multiple ReferenceTo targets persists the canonical polymorphic shape:
// FieldMetadata.Type = "reference", FieldMetadata.IsPolymorphic = true,
// and a read-only discriminator companion field is generated. This is the
// regression test for the tenant-overlay SSoT bypass finding.
func TestBuildTenantFieldMetadata_LookupAliasMultiTargetPersistsCanonicalShape(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	fields := buildTenantFieldMetadata([]packs.ObjectSpec{{
		APIName:   "Ticket__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName:     "LinkedTo__c",
			Label:       "Linked To",
			Type:        "lookup",
			ReferenceTo: []string{"Account", "Contact"},
		}},
	}}, sm)

	var parentField *models.FieldMetadata
	var discriminatorField *models.FieldMetadata
	for _, f := range fields {
		if f.Field == nil {
			continue
		}
		switch f.Field.APIName {
		case "LinkedTo__c":
			parentField = f.Field
		case "LinkedTo__c_type":
			discriminatorField = f.Field
		}
	}

	if parentField == nil {
		t.Fatal("LinkedTo__c field metadata not found")
	}
	if parentField.Type != constants.FieldTypeReference {
		t.Fatalf("LinkedTo__c Type = %q, want %q (lookup alias must normalize to reference for persistence)", parentField.Type, constants.FieldTypeReference)
	}
	if !parentField.IsPolymorphic {
		t.Fatal("LinkedTo__c IsPolymorphic = false, want true (multi-target lookup must infer polymorphic ownership)")
	}

	if discriminatorField == nil {
		t.Fatal("LinkedTo__c_type discriminator companion field metadata not found")
	}
	if discriminatorField.Createable || discriminatorField.Updateable {
		t.Fatalf("LinkedTo__c_type Createable/Updateable = %v/%v, want false/false", discriminatorField.Createable, discriminatorField.Updateable)
	}
	if discriminatorField.GeneratedForPolymorphicReference != "LinkedTo__c" {
		t.Fatalf("LinkedTo__c_type GeneratedForPolymorphicReference = %q, want %q", discriminatorField.GeneratedForPolymorphicReference, "LinkedTo__c")
	}
}

// TestBuildTenantFieldMetadata_SingleTargetLookupAliasPersistsAsReference is
// the regression test for the single-target lookup alias reload-safety
// finding (Codex Review round-1 blocker). A tenant-overlay field authored
// with type "lookup" and a single ReferenceTo target must persist
// FieldMetadata.Type = "reference" and ColumnDefinition.LogicalType =
// "reference" so that runtime callers using constants.IsReferenceType
// recognize the field as a relationship after cache refresh or restart.
// Before the fix, only the polymorphic branch stamped canonical type,
// leaving single-target lookup aliases persisted as raw "lookup".
func TestBuildTenantFieldMetadata_SingleTargetLookupAliasPersistsAsReference(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	fields := buildTenantFieldMetadata([]packs.ObjectSpec{{
		APIName:   "Ticket__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName:     "Owner__c",
			Label:       "Owner",
			Type:        "lookup",
			ReferenceTo: []string{"User"},
		}},
	}}, sm)

	var parentField *models.FieldMetadata
	for _, f := range fields {
		if f.Field != nil && f.Field.APIName == "Owner__c" {
			parentField = f.Field
			break
		}
	}
	if parentField == nil {
		t.Fatal("Owner__c field metadata not found")
	}
	if parentField.Type != constants.FieldTypeReference {
		t.Fatalf("Owner__c Type = %q, want %q (single-target lookup alias must canonicalize to reference for reload safety)", parentField.Type, constants.FieldTypeReference)
	}
	if parentField.IsPolymorphic {
		t.Fatal("Owner__c IsPolymorphic = true, want false (single-target must not infer polymorphic ownership)")
	}
}

// TestBuildTenantColumnDefs_SingleTargetLookupAliasPersistsAsReference is the
// column-definition counterpart to the field-metadata regression test. The
// persisted LogicalType must be "reference" so that the schema scan reload
// path sees a canonical type, not the raw authoring alias.
func TestBuildTenantColumnDefs_SingleTargetLookupAliasPersistsAsReference(t *testing.T) {
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	defs := buildTenantColumnDefs([]packs.ObjectSpec{{
		APIName:   "Ticket__c",
		TableType: string(constants.TableTypeCustomObject),
		Category:  "tenant",
		Fields: []packs.FieldSpec{{
			APIName:     "Owner__c",
			Label:       "Owner",
			Type:        "lookup",
			ReferenceTo: []string{"User"},
		}},
	}}, sm)

	if len(defs) != 1 {
		t.Fatalf("defs len = %d, want 1", len(defs))
	}
	var parentCol *domainschema.ColumnDefinition
	for i := range defs[0].Columns {
		if defs[0].Columns[i].Name == "Owner__c" {
			parentCol = &defs[0].Columns[i]
			break
		}
	}
	if parentCol == nil {
		t.Fatalf("Owner__c column not found in defs: %#v", defs[0].Columns)
	}
	if parentCol.LogicalType != string(constants.FieldTypeReference) {
		t.Fatalf("Owner__c LogicalType = %q, want %q (single-target lookup alias must canonicalize to reference for reload safety)", parentCol.LogicalType, constants.FieldTypeReference)
	}
	if parentCol.IsPolymorphic {
		t.Fatal("Owner__c IsPolymorphic = true, want false (single-target must not infer polymorphic ownership)")
	}
	if tenantColumnExists(defs[0].Columns, "Owner__c_type") {
		t.Fatal("Owner__c_type discriminator column unexpectedly generated for single-target lookup")
	}
}

// TestCanonicalizeSpecFieldTypesLookupToReference proves that the verify-path
// canonicalization mirrors the write-path resolver. A lookup-authored field
// in the spec must normalize to reference before specdrift compares it against
// live _System_Field.Type, or round-trip verify drift-fails.
func TestCanonicalizeSpecFieldTypesLookupToReference(t *testing.T) {
	specs := []packs.ObjectSpec{{
		APIName:   "Issue__c",
		TableType: string(constants.TableTypeCustomObject),
		Fields: []packs.FieldSpec{
			{
				APIName:     "Account__c",
				Label:       "Account",
				Type:        "lookup",
				ReferenceTo: []string{"Account", "Contact"},
			},
			{
				APIName:     "Owner__c",
				Label:       "Owner",
				Type:        "reference",
				ReferenceTo: []string{"User"},
			},
			{
				APIName: "Name",
				Label:   "Name",
				Type:    "string",
			},
		},
	}}

	canonicalizeSpecFieldTypes(specs)

	if specs[0].Fields[0].Type != string(constants.FieldTypeReference) {
		t.Fatalf("lookup field Type = %q, want %q", specs[0].Fields[0].Type, constants.FieldTypeReference)
	}
	if specs[0].Fields[1].Type != string(constants.FieldTypeReference) {
		t.Fatalf("reference field Type = %q, want %q", specs[0].Fields[1].Type, constants.FieldTypeReference)
	}
	if specs[0].Fields[2].Type != "string" {
		t.Fatalf("string field Type = %q, want %q", specs[0].Fields[2].Type, "string")
	}
}
