package analytics

import (
	"strings"
	"testing"

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

func TestMakeJoinAliasSanitizesAndDeduplicates(t *testing.T) {
	used := map[string]bool{}

	if got := makeJoinAlias("Parent Record__r", "j_", used); got != "j_parent_record__r" {
		t.Fatalf("first alias = %q, want j_parent_record__r", got)
	}
	if got := makeJoinAlias("Parent-Record__r", "j_", used); got != "j_parent_record__r_2" {
		t.Fatalf("collision alias = %q, want j_parent_record__r_2", got)
	}
	if got := makeJoinAlias("Parent Record__r", "dj_", map[string]bool{}); got != "dj_parent_record__r" {
		t.Fatalf("deep alias = %q, want dj_parent_record__r", got)
	}
}

func TestRelationshipNameForFieldPrefersMetadata(t *testing.T) {
	relName := "Reviewer"
	field := models.FieldMetadata{
		APIName:          "Reviewed_By__c",
		RelationshipName: &relName,
	}

	if got := relationshipNameForField(field); got != relName {
		t.Fatalf("relationshipNameForField() = %q, want %q", got, relName)
	}
}

func TestRelationshipTraversalNamesForFieldIncludeExplicitAndDerived(t *testing.T) {
	relName := "Leads"
	field := models.FieldMetadata{
		APIName:          "AssociateLead__c",
		RelationshipName: &relName,
	}

	got := relationshipTraversalNamesForField(field)
	wantSeen := map[string]bool{
		"Leads":            false,
		"AssociateLead__r": false,
	}
	for _, name := range got {
		if _, ok := wantSeen[name]; ok {
			wantSeen[name] = true
		}
	}
	for want, seen := range wantSeen {
		if !seen {
			t.Fatalf("relationshipTraversalNamesForField() = %v, missing %q", got, want)
		}
	}
}

func TestResolveAggregateForeignKeyUsesMetadataReference(t *testing.T) {
	parent := &models.ObjectMetadata{APIName: "Issue"}
	child := &models.ObjectMetadata{
		APIName: "WorkLog__c",
		Fields: []models.FieldMetadata{
			{
				APIName:     "ParentTicket__c",
				Type:        constants.FieldTypeReference,
				ReferenceTo: []string{"Issue"},
			},
			{
				APIName: "Hours__c",
				Type:    constants.FieldTypeDouble,
			},
		},
	}

	got, err := resolveAggregateForeignKey(parent, child, "")
	if err != nil {
		t.Fatalf("resolveAggregateForeignKey() error = %v", err)
	}
	if got != "ParentTicket__c" {
		t.Fatalf("resolveAggregateForeignKey() = %q, want ParentTicket__c", got)
	}
}

func TestResolveAggregateForeignKeyRejectsAmbiguousMetadata(t *testing.T) {
	parent := &models.ObjectMetadata{APIName: "Issue"}
	child := &models.ObjectMetadata{
		APIName: "IssueLink__c",
		Fields: []models.FieldMetadata{
			{
				APIName:     "SourceIssue__c",
				Type:        constants.FieldTypeReference,
				ReferenceTo: []string{"Issue"},
			},
			{
				APIName:     "TargetIssue__c",
				Type:        constants.FieldTypeReference,
				ReferenceTo: []string{"Issue"},
			},
		},
	}

	_, err := resolveAggregateForeignKey(parent, child, "")
	if err == nil {
		t.Fatal("resolveAggregateForeignKey() error = nil, want ambiguity failure")
	}
	if !strings.Contains(err.Error(), "multiple reference fields") {
		t.Fatalf("resolveAggregateForeignKey() error = %q, want ambiguity failure", err.Error())
	}
}

func TestResolveReferencedFieldMapsLogicalNameToMetadataNameField(t *testing.T) {
	got, err := resolveReferencedField(&models.ObjectMetadata{
		APIName: "Person",
		Fields: []models.FieldMetadata{
			{
				APIName:     "DisplayLabel__c",
				Type:        constants.FieldTypeString,
				IsNameField: true,
			},
		},
	}, constants.FieldName)
	if err != nil {
		t.Fatalf("resolveReferencedField() error = %v", err)
	}
	if got != "DisplayLabel__c" {
		t.Fatalf("resolveReferencedField() = %q, want DisplayLabel__c", got)
	}
}

func TestResolveReferencedFieldPrefersExactNameField(t *testing.T) {
	got, err := resolveReferencedField(&models.ObjectMetadata{
		APIName: "Person",
		Fields: []models.FieldMetadata{
			{
				APIName:     constants.FieldName,
				Type:        constants.FieldTypeString,
				IsNameField: false,
			},
			{
				APIName:     "DisplayLabel__c",
				Type:        constants.FieldTypeString,
				IsNameField: true,
			},
		},
	}, constants.FieldName)
	if err != nil {
		t.Fatalf("resolveReferencedField() error = %v", err)
	}
	if got != constants.FieldName {
		t.Fatalf("resolveReferencedField() = %q, want Name", got)
	}
}

// TestRelationshipNameForFieldFallsBackToGenericSuffixForCRMAndNonCRMFields
// locks in issue #69 acceptance criterion 2: when a field has no explicit
// RelationshipName, derivation must use the generic __c → __r suffix rule
// for both CRM-named fields (e.g. Account__c) and non-CRM app-pack fields
// (e.g. Issue__c), with no Salesforce standard-object lookup. Reintroducing
// a hardcoded CRM whitelist would diverge these cases.
func TestRelationshipNameForFieldFallsBackToGenericSuffixForCRMAndNonCRMFields(t *testing.T) {
	cases := []struct {
		apiName string
		want    string
	}{
		{"Account__c", "Account__r"},
		{"Issue__c", "Issue__r"},
	}
	for _, c := range cases {
		got := relationshipNameForField(models.FieldMetadata{APIName: c.apiName})
		if got != c.want {
			t.Fatalf("relationshipNameForField(%q) = %q, want %q (must use generic __c→__r rule)", c.apiName, got, c.want)
		}
	}
}

func TestResolveReferencedFieldRejectsMissingLeaf(t *testing.T) {
	_, err := resolveReferencedField(&models.ObjectMetadata{
		APIName: "Person",
		Fields: []models.FieldMetadata{
			{APIName: "DisplayLabel__c", Type: constants.FieldTypeString},
		},
	}, "DoesNotExist__c")
	if err == nil {
		t.Fatal("resolveReferencedField() error = nil, want missing field failure")
	}
	if !strings.Contains(err.Error(), "field \"DoesNotExist__c\" not found") {
		t.Fatalf("resolveReferencedField() error = %q, want missing field failure", err.Error())
	}
}
