package searchmetadata

import (
	"fmt"
	"strings"
	"testing"

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

func TestResolveSOSLSearchFieldsALLUsesPackDeclaredExactDefaults(t *testing.T) {
	ResetPackSearchAllDefaultFieldsForTest()
	t.Cleanup(ResetPackSearchAllDefaultFieldsForTest)

	if err := RegisterPackSearchAllDefaultFields("pingcap", []SearchAllDefaultFieldsDeclaration{{
		ObjectAPIName: "Lead",
		Fields:        []string{"id", "Name", constants.FieldOwnerID, "Email", "Phone", "Company", "Description"},
	}}); err != nil {
		t.Fatalf("RegisterPackSearchAllDefaultFields: %v", err)
	}

	resolution, err := ResolveSOSLSearchFields(leadSearchDefaultTestSchema(), "ALL")
	if err != nil {
		t.Fatalf("ResolveSOSLSearchFields: %v", err)
	}
	want := []string{constants.FieldID, constants.FieldName, constants.FieldOwnerID, "Email", "Phone", "Company", "Description"}
	if !equalSearchMetadataStrings(resolution.Fields, want) {
		t.Fatalf("fields = %#v, want exact pack declaration %#v", resolution.Fields, want)
	}
	if !resolution.Declared || resolution.LegacyFallback || resolution.Diagnostic != "" {
		t.Fatalf("resolution flags = %+v, want declared exact result without fallback/diagnostic", resolution)
	}
}

func TestResolveSOSLSearchFieldsDeclaredMissingFieldFailsNoFallback(t *testing.T) {
	ResetPackSearchAllDefaultFieldsForTest()
	t.Cleanup(ResetPackSearchAllDefaultFieldsForTest)

	if err := RegisterPackSearchAllDefaultFields("pingcap", []SearchAllDefaultFieldsDeclaration{{
		ObjectAPIName: "Lead",
		Fields:        []string{constants.FieldID, "Bogus__c"},
	}}); err != nil {
		t.Fatalf("RegisterPackSearchAllDefaultFields: %v", err)
	}

	_, err := ResolveSOSLSearchFields(leadSearchDefaultTestSchema(), "ALL")
	if err == nil {
		t.Fatal("ResolveSOSLSearchFields succeeded, want declared missing-field failure")
	}
	if got := err.Error(); !strings.Contains(got, "Bogus__c") || strings.Contains(strings.ToLower(got), "fallback") {
		t.Fatalf("error = %q, want missing declared field without fallback", got)
	}
}

func TestResolveSOSLSearchFieldsDeclaredRejectsUnsearchableFieldsNoFallback(t *testing.T) {
	for _, tc := range []struct {
		name  string
		field string
	}{
		{name: "number", field: "AnnualRevenue"},
		{name: "date", field: "ClosedDate"},
		{name: "formula", field: "FormulaText__c"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			ResetPackSearchAllDefaultFieldsForTest()
			t.Cleanup(ResetPackSearchAllDefaultFieldsForTest)

			if err := RegisterPackSearchAllDefaultFields("pingcap", []SearchAllDefaultFieldsDeclaration{{
				ObjectAPIName: "Lead",
				Fields:        []string{tc.field},
			}}); err != nil {
				t.Fatalf("RegisterPackSearchAllDefaultFields: %v", err)
			}

			_, err := ResolveSOSLSearchFields(leadSearchDefaultTestSchema(), "ALL")
			if err == nil {
				t.Fatalf("ResolveSOSLSearchFields succeeded for declared %s field %q, want validation failure", tc.name, tc.field)
			}
			if got := err.Error(); !strings.Contains(got, tc.field) || strings.Contains(strings.ToLower(got), "legacy") || strings.Contains(strings.ToLower(got), "fallback") {
				t.Fatalf("error = %q, want illegal declared field %q without legacy fallback", got, tc.field)
			}
		})
	}
}

func TestResolveSOSLSearchFieldsUndeclaredLegacyFallbackHasHardCap(t *testing.T) {
	ResetPackSearchAllDefaultFieldsForTest()
	t.Cleanup(ResetPackSearchAllDefaultFieldsForTest)

	schema := &models.ObjectMetadata{
		APIName:    "LegacyWide__c",
		Searchable: true,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldName, Type: constants.FieldTypeString, IsNameField: true},
		},
	}
	for i := 0; i < DefaultAllLegacyFallbackFieldCap+1; i++ {
		schema.Fields = append(schema.Fields, models.FieldMetadata{
			APIName: fmt.Sprintf("Text%d__c", i),
			Type:    constants.FieldTypeString,
		})
	}

	_, err := ResolveSOSLSearchFields(schema, "ALL")
	if err == nil {
		t.Fatal("ResolveSOSLSearchFields succeeded, want fallback cap failure")
	}
	msg := err.Error()
	if !strings.Contains(msg, "LegacyWide__c") || !strings.Contains(msg, "legacy ALL search field fallback") || !strings.Contains(msg, "cap") {
		t.Fatalf("error = %q, want object-scoped fallback cap diagnostic", msg)
	}
}

func TestResolveSOSLSearchFieldsNonALLScopeIgnoresDeclaredDefaults(t *testing.T) {
	ResetPackSearchAllDefaultFieldsForTest()
	t.Cleanup(ResetPackSearchAllDefaultFieldsForTest)

	if err := RegisterPackSearchAllDefaultFields("pingcap", []SearchAllDefaultFieldsDeclaration{{
		ObjectAPIName: "Lead",
		Fields:        []string{constants.FieldID, constants.FieldName, "Email"},
	}}); err != nil {
		t.Fatalf("RegisterPackSearchAllDefaultFields: %v", err)
	}

	resolution, err := ResolveSOSLSearchFields(leadSearchDefaultTestSchema(), "EMAIL")
	if err != nil {
		t.Fatalf("ResolveSOSLSearchFields EMAIL: %v", err)
	}
	if !equalSearchMetadataStrings(resolution.Fields, []string{"Email"}) {
		t.Fatalf("EMAIL fields = %#v, want scoped email field only", resolution.Fields)
	}
	if resolution.Declared || resolution.LegacyFallback {
		t.Fatalf("EMAIL resolution flags = %+v, want scoped path outside ALL defaults", resolution)
	}
}

func leadSearchDefaultTestSchema() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName:    "Lead",
		Searchable: true,
		ListFields: []string{constants.FieldOwnerID, "Company", "Phone"},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, IsSystem: true},
			{APIName: constants.FieldName, Type: constants.FieldTypeString, IsNameField: true},
			{APIName: "Email", Type: constants.FieldTypeEmail},
			{APIName: "Phone", Type: constants.FieldTypePhone},
			{APIName: "Company", Type: constants.FieldTypeString},
			{APIName: "Description", Type: constants.FieldTypeTextarea},
			{APIName: "AnnualRevenue", Type: constants.FieldTypeDouble},
			{APIName: "ClosedDate", Type: constants.FieldTypeDate},
			{APIName: "FormulaText__c", Type: constants.FieldTypeFormula},
		},
	}
}

func equalSearchMetadataStrings(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}
