package metadatarel

import (
	"testing"

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

func TestResolveEffectiveRelationshipsDerivesBaseAndAppliesSupportedOverlay(t *testing.T) {
	restrict := constants.DeleteRuleRestrict
	accountRelLabel := "Customers"
	accountRelFields := `["Name","Email"]`
	customerName := "Customer"

	schema := &models.ObjectMetadata{
		APIName: "Contact",
		Fields: []models.FieldMetadata{
			{
				APIName:          "AccountId",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"Account"},
				RelationshipName: stringPtr("Account"),
			},
			{
				APIName:          "ParentId",
				Type:             constants.FieldTypeMasterDetail,
				IsMasterDetail:   true,
				ReferenceTo:      []string{"Account"},
				RelationshipName: stringPtr("Parent"),
			},
			{
				APIName:          "WhatId",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"Account", "Opportunity"},
				RelationshipName: stringPtr("What"),
				DeleteRule:       &restrict,
			},
			{APIName: "Name", Type: constants.FieldTypeString},
		},
	}

	got := ResolveEffectiveRelationships(schema, []*models.Relationship{
		{
			ChildObjectAPIName:  "Contact",
			ParentObjectAPIName: "Account",
			FieldAPIName:        "AccountId",
			RelationshipName:    customerName,
			RelationshipType:    string(constants.FieldTypeMasterDetail),
			RelatedListLabel:    &accountRelLabel,
			RelatedListFields:   &accountRelFields,
		},
		{
			ChildObjectAPIName:  "Contact",
			ParentObjectAPIName: "Account",
			FieldAPIName:        "ParentId",
			RestrictedDelete:    true,
		},
		{
			ChildObjectAPIName:  "Contact",
			ParentObjectAPIName: "User",
			FieldAPIName:        "OwnerId",
			RelationshipName:    "Owner",
		},
	})

	if len(got) != 4 {
		t.Fatalf("len(ResolveEffectiveRelationships) = %d, want 4", len(got))
	}

	byKey := make(map[string]*models.Relationship, len(got))
	for _, rel := range got {
		byKey[rel.ChildObjectAPIName+"."+rel.FieldAPIName+"->"+rel.ParentObjectAPIName] = rel
	}

	accountRel := byKey["Contact.AccountId->Account"]
	if accountRel == nil {
		t.Fatal("missing Contact.AccountId->Account relationship")
	}
	if accountRel.RelationshipName != "Customer" {
		t.Fatalf("AccountId relationshipName = %q, want Customer", accountRel.RelationshipName)
	}
	if accountRel.RelatedListLabel == nil || *accountRel.RelatedListLabel != accountRelLabel {
		t.Fatalf("AccountId relatedListLabel = %#v, want %q", accountRel.RelatedListLabel, accountRelLabel)
	}
	if accountRel.RelatedListFields == nil || *accountRel.RelatedListFields != accountRelFields {
		t.Fatalf("AccountId relatedListFields = %#v, want %q", accountRel.RelatedListFields, accountRelFields)
	}
	if accountRel.RelationshipType != string(constants.FieldTypeReference) {
		t.Fatalf("AccountId relationshipType = %q, want %q", accountRel.RelationshipType, constants.FieldTypeReference)
	}
	if !accountRel.RestrictedDelete {
		t.Fatal("AccountId RestrictedDelete = false, want default restrict")
	}

	parentRel := byKey["Contact.ParentId->Account"]
	if parentRel == nil {
		t.Fatal("missing Contact.ParentId->Account relationship")
	}
	if !parentRel.IsSharingParent {
		t.Fatal("ParentId IsSharingParent = false, want true")
	}
	if parentRel.CascadeDelete {
		t.Fatal("ParentId CascadeDelete = true after explicit restricted overlay, want false")
	}
	if !parentRel.RestrictedDelete {
		t.Fatal("ParentId RestrictedDelete = false after explicit overlay, want true")
	}

	whatAccount := byKey["Contact.WhatId->Account"]
	whatOpportunity := byKey["Contact.WhatId->Opportunity"]
	if whatAccount == nil || whatOpportunity == nil {
		t.Fatalf("polymorphic WhatId relationships missing: account=%v opportunity=%v", whatAccount != nil, whatOpportunity != nil)
	}
	if !whatAccount.RestrictedDelete || !whatOpportunity.RestrictedDelete {
		t.Fatal("WhatId restricted delete should derive from field delete rule for every target")
	}
	if _, ok := byKey["Contact.OwnerId->User"]; ok {
		t.Fatal("orphan explicit relationship row was materialized without backing field metadata")
	}
}

func TestResolveEffectiveRelationshipsDerivesCustomTraversalName(t *testing.T) {
	got := ResolveEffectiveRelationships(&models.ObjectMetadata{
		APIName: "Line__c",
		Fields: []models.FieldMetadata{
			{
				APIName:     "Project__c",
				Type:        constants.FieldTypeReference,
				ReferenceTo: []string{"Project__c"},
			},
		},
	}, nil)

	if len(got) != 1 {
		t.Fatalf("len(ResolveEffectiveRelationships) = %d, want 1", len(got))
	}
	if got[0].RelationshipName != "Project__r" {
		t.Fatalf("RelationshipName = %q, want Project__r", got[0].RelationshipName)
	}
}

func stringPtr(v string) *string {
	return &v
}

func deleteRulePtr(v constants.DeleteRule) *constants.DeleteRule {
	return &v
}

func TestResolveDeleteSemanticsDefaultsAndPrecedence(t *testing.T) {
	cascade := constants.DeleteRuleCascade
	restrict := constants.DeleteRuleRestrict
	setNull := constants.DeleteRuleSetNull

	tests := []struct {
		name           string
		field          models.FieldMetadata
		rel            *models.Relationship
		wantCascade    bool
		wantRestricted bool
		wantRule       constants.DeleteRule
	}{
		{
			name:           "ambiguous relationship defaults restrict",
			field:          models.FieldMetadata{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent"}},
			rel:            &models.Relationship{CascadeDelete: false, RestrictedDelete: false},
			wantRestricted: true,
			wantRule:       constants.DeleteRuleRestrict,
		},
		{
			name:           "both true fails closed restrict",
			field:          models.FieldMetadata{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent"}},
			rel:            &models.Relationship{CascadeDelete: true, RestrictedDelete: true},
			wantRestricted: true,
			wantRule:       constants.DeleteRuleRestrict,
		},
		{
			name:        "relationship cascade",
			field:       models.FieldMetadata{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent"}},
			rel:         &models.Relationship{CascadeDelete: true},
			wantCascade: true,
			wantRule:    constants.DeleteRuleCascade,
		},
		{
			name:        "master detail cascades",
			field:       models.FieldMetadata{APIName: "ParentId", Type: constants.FieldTypeMasterDetail, ReferenceTo: []string{"Parent"}},
			wantCascade: true,
			wantRule:    constants.DeleteRuleCascade,
		},
		{
			name:        "explicit field cascade",
			field:       models.FieldMetadata{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent"}, DeleteRule: &cascade},
			wantCascade: true,
			wantRule:    constants.DeleteRuleCascade,
		},
		{
			name:           "explicit field restrict",
			field:          models.FieldMetadata{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent"}, DeleteRule: &restrict},
			wantRestricted: true,
			wantRule:       constants.DeleteRuleRestrict,
		},
		{
			name:     "explicit field set null is honored",
			field:    models.FieldMetadata{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent"}, DeleteRule: &setNull},
			wantRule: constants.DeleteRuleSetNull,
		},
		{
			name:     "physical set null spelling is normalized",
			field:    models.FieldMetadata{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent"}, DeleteRule: deleteRulePtr(constants.DeleteRule("SET NULL"))},
			wantRule: constants.DeleteRuleSetNull,
		},
		{
			name: "non relationship has no delete semantics",
			field: models.FieldMetadata{
				APIName: "Name",
				Type:    constants.FieldTypeString,
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got := ResolveDeleteSemantics(tc.field, tc.rel)
			if got.Cascade != tc.wantCascade || got.Restricted != tc.wantRestricted || got.Rule != tc.wantRule {
				t.Fatalf("ResolveDeleteSemantics() = %+v, want cascade=%v restricted=%v rule=%q", got, tc.wantCascade, tc.wantRestricted, tc.wantRule)
			}
		})
	}
}

func TestResolveFieldDeleteSemanticsAggregatesMultiTargetRelationships(t *testing.T) {
	field := models.FieldMetadata{
		APIName:     "WhatId",
		Type:        constants.FieldTypeReference,
		ReferenceTo: []string{"Account", "Opportunity"},
	}

	got := ResolveFieldDeleteSemantics(field, []*models.Relationship{
		{ParentObjectAPIName: "Account", FieldAPIName: "WhatId", CascadeDelete: true},
		{ParentObjectAPIName: "Opportunity", FieldAPIName: "WhatId"},
	})
	if !got.Restricted || got.Rule != constants.DeleteRuleRestrict {
		t.Fatalf("mixed target semantics = %+v, want restrict", got)
	}

	got = ResolveFieldDeleteSemantics(field, []*models.Relationship{
		{ParentObjectAPIName: "Account", FieldAPIName: "WhatId", CascadeDelete: true},
		{ParentObjectAPIName: "Opportunity", FieldAPIName: "WhatId", CascadeDelete: true},
	})
	if !got.Cascade || got.Rule != constants.DeleteRuleCascade {
		t.Fatalf("all cascade semantics = %+v, want cascade", got)
	}

	setNull := constants.DeleteRuleSetNull
	field.DeleteRule = &setNull
	got = ResolveFieldDeleteSemantics(field, []*models.Relationship{
		{ParentObjectAPIName: "Account", FieldAPIName: "WhatId"},
		{ParentObjectAPIName: "Opportunity", FieldAPIName: "WhatId"},
	})
	if got.Cascade || got.Restricted || !got.SetNull || got.Rule != constants.DeleteRuleSetNull {
		t.Fatalf("all set-null semantics = %+v, want set null", got)
	}
}

func TestFieldReferencesParentUsesPersistedRelationshipSemantics(t *testing.T) {
	field := models.FieldMetadata{
		APIName:        "ParentId",
		Type:           constants.FieldTypeReference,
		IsMasterDetail: true,
		ReferenceTo:    []string{"Parent"},
	}

	if !FieldReferencesParent(field, "parent") {
		t.Fatal("FieldReferencesParent did not match reference+IsMasterDetail field case-insensitively")
	}
	if FieldReferencesParent(models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString}, "Parent") {
		t.Fatal("FieldReferencesParent matched a non-relationship field")
	}
}
