package analytics

import (
	"testing"

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

// strPtr is a tiny helper local to the test file — we deliberately do not
// import any production helper so the test never gets coupled to internals.
func strPtr(s string) *string { return &s }

func newRefField(api string, parents []string, childRel *string) models.FieldMetadata {
	return models.FieldMetadata{
		APIName:               api,
		Type:                  constants.FieldTypeReference,
		ReferenceTo:           parents,
		ChildRelationshipName: childRel,
	}
}

// TestResolveChildRelationship_F1_CaseA covers the "no candidates" case:
// no FK on any child object points at the requested parent. The caller
// should treat this as a 400 INVALID_QUERY ("No such relationship 'X' on
// entity 'Y'") — which downstream code derives from
// ChildRelStatusNoCandidates.
func TestResolveChildRelationship_F1_CaseA_NoCandidates(t *testing.T) {
	schemas := []*models.ObjectMetadata{
		{
			APIName: "Contact",
			Fields: []models.FieldMetadata{
				newRefField("AccountId", []string{"Account"}, strPtr("Contacts")),
			},
		},
	}
	got := ResolveChildRelationshipAuthoritative(schemas, "Opportunity", "Wat")
	if got.Status != ChildRelStatusNoCandidates {
		t.Fatalf("status = %v; want NoCandidates", got.Status)
	}
}

// TestResolveChildRelationship_F1_CaseB_Match covers the populated-match
// case. The matched candidate is authoritative even if a NULL sibling on
// the same parent exists — that is F1's explicit note.
func TestResolveChildRelationship_F1_CaseB_PopulatedMatch_NullSiblingDoesNotPromote(t *testing.T) {
	schemas := []*models.ObjectMetadata{
		{
			APIName: "Contact",
			Fields: []models.FieldMetadata{
				newRefField("AccountId", []string{"Account"}, strPtr("Contacts")),
			},
		},
		{
			APIName: "Opportunity",
			// NULL ChildRelationshipName on a sibling FK pointing at Account.
			// F1 requires this NOT to promote a Contacts match to 500.
			Fields: []models.FieldMetadata{
				newRefField("AccountId", []string{"Account"}, nil),
			},
		},
	}
	got := ResolveChildRelationshipAuthoritative(schemas, "Account", "Contacts")
	if got.Status != ChildRelStatusMatched {
		t.Fatalf("status = %v; want Matched (NULL sibling must NOT promote to PackReapplyRequired)", got.Status)
	}
	if got.ChildObjectAPIName != "Contact" {
		t.Errorf("ChildObjectAPIName = %q; want Contact", got.ChildObjectAPIName)
	}
	if got.FKFieldAPIName != "AccountId" {
		t.Errorf("FKFieldAPIName = %q; want AccountId", got.FKFieldAPIName)
	}
}

func TestResolveChildRelationship_F1_CaseB_CaseInsensitive(t *testing.T) {
	schemas := []*models.ObjectMetadata{
		{
			APIName: "Contact",
			Fields: []models.FieldMetadata{
				newRefField("accountid", []string{"account"}, strPtr("Contacts")),
			},
		},
	}
	got := ResolveChildRelationshipAuthoritative(schemas, "Account", "contacts")
	if got.Status != ChildRelStatusMatched {
		t.Fatalf("status = %v; want Matched", got.Status)
	}
}

// TestResolveChildRelationship_F1_CaseC covers the substrate-not-converged
// case: candidates exist, none matches, and at least one has NULL
// ChildRelationshipName. This is the upgrade-path hazard the F1 table is
// designed to catch — caller should map it to 500 PACK_REAPPLY_REQUIRED.
func TestResolveChildRelationship_F1_CaseC_AnyNullPromotesTo500(t *testing.T) {
	schemas := []*models.ObjectMetadata{
		{
			APIName: "Contact",
			Fields: []models.FieldMetadata{
				newRefField("AccountId", []string{"Account"}, strPtr("Contacts")),
			},
		},
		{
			APIName: "Opportunity",
			Fields: []models.FieldMetadata{
				// NULL — substrate not yet populated for this FK.
				newRefField("AccountId", []string{"Account"}, nil),
			},
		},
	}
	got := ResolveChildRelationshipAuthoritative(schemas, "Account", "Opportunities")
	if got.Status != ChildRelStatusPackReapplyRequired {
		t.Fatalf("status = %v; want PackReapplyRequired", got.Status)
	}
}

// TestResolveChildRelationship_F1_CaseD covers the "user typo" case: every
// candidate has ChildRelationshipName populated, and none matches the
// requested name. Caller should map to 400 INVALID_QUERY.
func TestResolveChildRelationship_F1_CaseD_AllPopulatedNoMatch(t *testing.T) {
	schemas := []*models.ObjectMetadata{
		{
			APIName: "Contact",
			Fields: []models.FieldMetadata{
				newRefField("AccountId", []string{"Account"}, strPtr("Contacts")),
			},
		},
		{
			APIName: "Opportunity",
			Fields: []models.FieldMetadata{
				newRefField("AccountId", []string{"Account"}, strPtr("Opportunities")),
			},
		},
	}
	got := ResolveChildRelationshipAuthoritative(schemas, "Account", "Wat")
	if got.Status != ChildRelStatusNoMatch {
		t.Fatalf("status = %v; want NoMatch", got.Status)
	}
}

// TestResolveChildRelationship_SelfReference matches an irregular case the
// historical inferRelationshipName heuristic could not handle: Account.ParentId
// publishes "ChildAccounts" (not "Accounts"). The authoritative resolver
// must succeed when the substrate carries the real value.
func TestResolveChildRelationship_SelfReference_ChildAccounts(t *testing.T) {
	schemas := []*models.ObjectMetadata{
		{
			APIName: "Account",
			Fields: []models.FieldMetadata{
				newRefField("ParentId", []string{"Account"}, strPtr("ChildAccounts")),
			},
		},
	}
	got := ResolveChildRelationshipAuthoritative(schemas, "Account", "ChildAccounts")
	if got.Status != ChildRelStatusMatched {
		t.Fatalf("status = %v; want Matched", got.Status)
	}
	if got.ChildObjectAPIName != "Account" || got.FKFieldAPIName != "ParentId" {
		t.Errorf("got (%q, %q); want (Account, ParentId)",
			got.ChildObjectAPIName, got.FKFieldAPIName)
	}
}

func TestResolveChildRelationship_MasterDetail_TreatedAsCandidate(t *testing.T) {
	mdField := models.FieldMetadata{
		APIName:               "AccountId",
		Type:                  constants.FieldTypeMasterDetail,
		ReferenceTo:           []string{"Account"},
		ChildRelationshipName: strPtr("OpportunityLineItems"),
	}
	schemas := []*models.ObjectMetadata{
		{APIName: "OpportunityLineItem", Fields: []models.FieldMetadata{mdField}},
	}
	got := ResolveChildRelationshipAuthoritative(schemas, "Account", "OpportunityLineItems")
	if got.Status != ChildRelStatusMatched {
		t.Fatalf("status = %v; want Matched (master-detail must be treated as candidate)", got.Status)
	}
}

func TestResolveChildRelationship_NonReferenceField_Ignored(t *testing.T) {
	schemas := []*models.ObjectMetadata{
		{
			APIName: "Contact",
			Fields: []models.FieldMetadata{
				{APIName: "Name", Type: constants.FieldTypeString},
			},
		},
	}
	got := ResolveChildRelationshipAuthoritative(schemas, "Account", "Contacts")
	if got.Status != ChildRelStatusNoCandidates {
		t.Fatalf("status = %v; want NoCandidates (string field must not be a candidate)", got.Status)
	}
}

func TestResolveChildRelationship_ProcessInstanceTypoNoMatchWhenLegacyWorkitemPopulated(t *testing.T) {
	schemas := []*models.ObjectMetadata{
		{
			APIName: constants.TableProcessInstanceHistory,
			Fields: []models.FieldMetadata{
				newRefField(constants.FieldProcessInstanceHistory_ProcessInstanceID, []string{constants.TableProcessInstance}, strPtr("StepsAndWorkitems")),
			},
		},
		{
			APIName: constants.TableProcessInstanceWorkitem,
			Fields: []models.FieldMetadata{
				newRefField(constants.FieldProcessInstanceWorkitem_ProcessInstanceID, []string{constants.TableProcessInstance}, strPtr("PendingWorkitems")),
			},
		},
	}

	got := ResolveChildRelationshipAuthoritative(schemas, constants.TableProcessInstance, "Stepworkitems")
	if got.Status != ChildRelStatusNoMatch {
		t.Fatalf("status = %v; want NoMatch for populated ProcessInstance child relationships", got.Status)
	}
}
