package searchmetadata

import (
	"fmt"
	"sort"
	"strings"
	"sync"

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

const DefaultAllLegacyFallbackFieldCap = 32

// SearchAllDefaultFieldsDeclaration is the source-backed pack declaration for
// the ordered field set used by SOSL IN ALL FIELDS for one object. It is exact:
// declared objects do not expand to list_fields/search_scope_order/all fields.
type SearchAllDefaultFieldsDeclaration struct {
	ObjectAPIName string   `json:"objectApiName"`
	Fields        []string `json:"fields"`
}

type SearchFieldsResolution struct {
	Fields         []string
	Declared       bool
	LegacyFallback bool
	Diagnostic     string
}

type SearchFieldValidationError struct {
	Field   string
	Message string
}

func (e SearchFieldValidationError) Error() string {
	return e.Message
}

func (e SearchFieldValidationError) ValidationField() string {
	if strings.TrimSpace(e.Field) == "" {
		return "searchField"
	}
	return e.Field
}

func (e SearchFieldValidationError) ValidationMessage() string {
	return e.Message
}

var (
	packSearchAllDefaultFieldsMu sync.RWMutex
	packSearchAllDefaultFields   map[string][]SearchAllDefaultFieldsDeclaration
)

func RegisterPackSearchAllDefaultFields(owner string, declarations []SearchAllDefaultFieldsDeclaration) error {
	owner = strings.TrimSpace(owner)
	if owner == "" {
		return errInvalidSearchAllDefaultFields("missing owner")
	}
	if err := ValidatePackSearchAllDefaultFields(declarations); err != nil {
		return err
	}
	copied := cloneSearchAllDefaultFieldsDeclarations(declarations)

	packSearchAllDefaultFieldsMu.Lock()
	if packSearchAllDefaultFields == nil {
		packSearchAllDefaultFields = map[string][]SearchAllDefaultFieldsDeclaration{}
	}
	if err := validateSearchAllDefaultFieldsConflictsLocked(owner, copied); err != nil {
		packSearchAllDefaultFieldsMu.Unlock()
		return err
	}
	if len(copied) == 0 {
		delete(packSearchAllDefaultFields, owner)
	} else {
		packSearchAllDefaultFields[owner] = copied
	}
	packSearchAllDefaultFieldsMu.Unlock()
	return nil
}

func ValidatePackSearchAllDefaultFields(declarations []SearchAllDefaultFieldsDeclaration) error {
	seenObjects := make(map[string]string, len(declarations))
	for _, decl := range declarations {
		objectName := strings.TrimSpace(decl.ObjectAPIName)
		if objectName == "" {
			return errInvalidSearchAllDefaultFields("missing objectApiName")
		}
		objectKey := strings.ToLower(objectName)
		if existing, dup := seenObjects[objectKey]; dup {
			return errInvalidSearchAllDefaultFields(fmt.Sprintf("duplicate object %s", existing))
		}
		seenObjects[objectKey] = objectName
		if len(decl.Fields) == 0 {
			return errInvalidSearchAllDefaultFields(fmt.Sprintf("object %s has no fields", objectName))
		}
		seenFields := make(map[string]string, len(decl.Fields))
		for _, rawField := range decl.Fields {
			field := strings.TrimSpace(rawField)
			if field == "" {
				return errInvalidSearchAllDefaultFields(fmt.Sprintf("object %s has empty field", objectName))
			}
			key := strings.ToLower(field)
			if existing, dup := seenFields[key]; dup {
				return errInvalidSearchAllDefaultFields(fmt.Sprintf("object %s has duplicate field %s", objectName, existing))
			}
			seenFields[key] = field
		}
	}
	return nil
}

func ValidatePackSearchAllDefaultFieldsOwnership(declarations []SearchAllDefaultFieldsDeclaration, ownedObjectAPINames []string) error {
	if len(declarations) == 0 {
		return nil
	}
	owned := make(map[string]string, len(ownedObjectAPINames))
	for _, objectAPIName := range ownedObjectAPINames {
		objectAPIName = strings.TrimSpace(objectAPIName)
		if objectAPIName == "" {
			continue
		}
		owned[strings.ToLower(objectAPIName)] = objectAPIName
	}
	for _, decl := range declarations {
		name := strings.TrimSpace(decl.ObjectAPIName)
		if name == "" {
			continue
		}
		if _, ok := owned[strings.ToLower(name)]; !ok {
			return errInvalidSearchAllDefaultFields(fmt.Sprintf("object %s is not owned by the declaring pack", name))
		}
	}
	return nil
}

func ClearPackSearchAllDefaultFields() {
	packSearchAllDefaultFieldsMu.Lock()
	packSearchAllDefaultFields = nil
	packSearchAllDefaultFieldsMu.Unlock()
}

func ResetPackSearchAllDefaultFieldsForTest() {
	ClearPackSearchAllDefaultFields()
}

func PackSearchAllDefaultFieldsForObject(objectAPIName string) ([]string, bool) {
	objectKey := strings.ToLower(strings.TrimSpace(objectAPIName))
	if objectKey == "" {
		return nil, false
	}
	packSearchAllDefaultFieldsMu.RLock()
	owners := make([]string, 0, len(packSearchAllDefaultFields))
	for owner := range packSearchAllDefaultFields {
		owners = append(owners, owner)
	}
	sort.Strings(owners)
	for _, owner := range owners {
		for _, decl := range packSearchAllDefaultFields[owner] {
			if strings.EqualFold(strings.TrimSpace(decl.ObjectAPIName), objectAPIName) {
				fields := append([]string(nil), decl.Fields...)
				packSearchAllDefaultFieldsMu.RUnlock()
				return fields, true
			}
		}
	}
	packSearchAllDefaultFieldsMu.RUnlock()
	return nil, false
}

func ResolveSOSLSearchFields(schema *models.ObjectMetadata, scope string) (SearchFieldsResolution, error) {
	if schema == nil {
		return SearchFieldsResolution{}, nil
	}
	if !strings.EqualFold(strings.TrimSpace(scope), "ALL") {
		return SearchFieldsResolution{Fields: scopedSOSLSearchFields(schema, scope)}, nil
	}

	if declaredFields, ok := PackSearchAllDefaultFieldsForObject(schema.APIName); ok {
		fields, err := canonicalDeclaredAllDefaultFields(schema, declaredFields)
		if err != nil {
			return SearchFieldsResolution{}, err
		}
		return SearchFieldsResolution{Fields: fields, Declared: true}, nil
	}

	fields := legacyAllSearchFields(schema)
	if err := validateLegacyAllSearchFieldsWithinFallbackCap(schema, len(fields)); err != nil {
		return SearchFieldsResolution{}, err
	}
	diagnostic := ""
	if len(fields) > 0 {
		diagnostic = fmt.Sprintf("object %s used legacy ALL search field fallback with %d fields; add a search_all_default_fields pack declaration", schema.APIName, len(fields))
	}
	return SearchFieldsResolution{Fields: fields, LegacyFallback: true, Diagnostic: diagnostic}, nil
}

func ResolveDeclaredSearchAllDefaultFieldsForSchema(schema *models.ObjectMetadata, fields []string) ([]string, error) {
	return canonicalDeclaredAllDefaultFields(schema, fields)
}

func ValidateLegacyAllSearchFieldsWithinFallbackCap(schema *models.ObjectMetadata) error {
	if schema == nil {
		return nil
	}
	return validateLegacyAllSearchFieldsWithinFallbackCap(schema, len(legacyAllSearchFields(schema)))
}

func validateLegacyAllSearchFieldsWithinFallbackCap(schema *models.ObjectMetadata, fieldCount int) error {
	if fieldCount <= DefaultAllLegacyFallbackFieldCap {
		return nil
	}
	msg := fmt.Sprintf("object %s legacy ALL search field fallback expanded to %d fields, exceeding cap %d; add a search_all_default_fields pack declaration", schema.APIName, fieldCount, DefaultAllLegacyFallbackFieldCap)
	return SearchFieldValidationError{Field: "searchField", Message: msg}
}

func canonicalDeclaredAllDefaultFields(schema *models.ObjectMetadata, fields []string) ([]string, error) {
	out := make([]string, 0, len(fields))
	seen := make(map[string]string, len(fields))
	allowedFields := allowedSearchAllDefaultFieldSet(schema)
	for _, rawField := range fields {
		apiName := strings.TrimSpace(rawField)
		field := models.FindField(schema, apiName)
		if field == nil {
			msg := fmt.Sprintf("object %s search_all_default_fields references missing field %s", schema.APIName, apiName)
			return nil, SearchFieldValidationError{Field: "searchField", Message: msg}
		}
		key := strings.ToLower(field.APIName)
		if _, ok := allowedFields[key]; !ok {
			msg := fmt.Sprintf("object %s search_all_default_fields references illegal field %s", schema.APIName, field.APIName)
			return nil, SearchFieldValidationError{Field: "searchField", Message: msg}
		}
		if existing, dup := seen[key]; dup {
			msg := fmt.Sprintf("object %s search_all_default_fields has duplicate field %s", schema.APIName, existing)
			return nil, SearchFieldValidationError{Field: "searchField", Message: msg}
		}
		seen[key] = field.APIName
		out = append(out, field.APIName)
	}
	return out, nil
}

func allowedSearchAllDefaultFieldSet(schema *models.ObjectMetadata) map[string]struct{} {
	out := make(map[string]struct{}, len(schema.Fields))
	for _, field := range schema.Fields {
		if IsAllowedSearchAllDefaultField(field) {
			out[strings.ToLower(field.APIName)] = struct{}{}
		}
	}
	for _, apiName := range legacyAllSearchFields(schema) {
		out[strings.ToLower(apiName)] = struct{}{}
	}
	return out
}

func IsAllowedSearchAllDefaultField(field models.FieldMetadata) bool {
	if strings.EqualFold(field.APIName, constants.FieldID) {
		return true
	}
	if field.IsSystem {
		return false
	}
	return IsSearchableField(field)
}

func scopedSOSLSearchFields(schema *models.ObjectMetadata, scope string) []string {
	fields := make([]string, 0)
	seen := make(map[string]struct{})
	addScoped := func(apiName string) {
		if apiName == "" {
			return
		}
		if _, ok := seen[apiName]; ok {
			return
		}
		seen[apiName] = struct{}{}
		fields = append(fields, apiName)
	}
	for _, field := range schema.Fields {
		switch strings.ToUpper(strings.TrimSpace(scope)) {
		case "NAME":
			if field.IsNameField || strings.EqualFold(field.APIName, constants.FieldName) {
				addScoped(field.APIName)
				addCompoundMembers(schema, field.APIName, addScoped)
			}
		case "EMAIL":
			if strings.EqualFold(string(field.Type), string(constants.FieldTypeEmail)) {
				addScoped(field.APIName)
			}
		case "PHONE":
			if strings.EqualFold(string(field.Type), string(constants.FieldTypePhone)) {
				addScoped(field.APIName)
			}
		}
	}
	return fields
}

func legacyAllSearchFields(schema *models.ObjectMetadata) []string {
	fields := make([]string, 0)
	seen := make(map[string]struct{})
	addScoped := func(apiName string) {
		if apiName == "" {
			return
		}
		if models.FindField(schema, apiName) == nil {
			return
		}
		if _, ok := seen[apiName]; ok {
			return
		}
		seen[apiName] = struct{}{}
		fields = append(fields, apiName)
	}
	add := func(apiName string) {
		field := models.FindField(schema, apiName)
		if field == nil || !IsSearchableField(*field) {
			return
		}
		addScoped(apiName)
	}

	for _, field := range schema.Fields {
		if field.IsNameField || strings.EqualFold(field.APIName, constants.FieldName) {
			addScoped(field.APIName)
			addCompoundMembers(schema, field.APIName, addScoped)
		}
	}
	for _, field := range schema.Fields {
		if strings.EqualFold(string(field.Type), string(constants.FieldTypeEmail)) ||
			strings.EqualFold(string(field.Type), string(constants.FieldTypePhone)) {
			addScoped(field.APIName)
		}
	}
	for _, apiName := range schema.ListFields {
		add(apiName)
	}
	for _, field := range schema.Fields {
		if field.IsSystem {
			continue
		}
		add(field.APIName)
	}
	return fields
}

func addCompoundMembers(schema *models.ObjectMetadata, apiName string, add func(string)) {
	for _, member := range compoundMemberNames(schema, apiName) {
		add(member)
	}
}

func compoundMemberNames(schema *models.ObjectMetadata, parentAPIName string) []string {
	if schema == nil || strings.TrimSpace(parentAPIName) == "" {
		return nil
	}
	if field := models.FindField(schema, parentAPIName); field != nil && field.IsCompound && len(field.ComposedFrom) > 0 {
		return canonicalSchemaFieldNames(schema, field.ComposedFrom)
	}
	isCompound, members := fieldprovenance.ClassifyCompound(schema.APIName, parentAPIName)
	if !isCompound || len(members) == 0 {
		return nil
	}
	return canonicalSchemaFieldNames(schema, members)
}

func canonicalSchemaFieldNames(schema *models.ObjectMetadata, names []string) []string {
	out := make([]string, 0, len(names))
	for _, name := range names {
		if field := models.FindField(schema, name); field != nil {
			out = append(out, field.APIName)
		}
	}
	return out
}

func cloneSearchAllDefaultFieldsDeclarations(declarations []SearchAllDefaultFieldsDeclaration) []SearchAllDefaultFieldsDeclaration {
	if len(declarations) == 0 {
		return nil
	}
	out := make([]SearchAllDefaultFieldsDeclaration, 0, len(declarations))
	for _, decl := range declarations {
		out = append(out, SearchAllDefaultFieldsDeclaration{
			ObjectAPIName: strings.TrimSpace(decl.ObjectAPIName),
			Fields:        trimStringSlice(decl.Fields),
		})
	}
	return out
}

func trimStringSlice(values []string) []string {
	if len(values) == 0 {
		return nil
	}
	out := make([]string, 0, len(values))
	for _, value := range values {
		out = append(out, strings.TrimSpace(value))
	}
	return out
}

func validateSearchAllDefaultFieldsConflictsLocked(owner string, declarations []SearchAllDefaultFieldsDeclaration) error {
	for existingOwner, existingDeclarations := range packSearchAllDefaultFields {
		if strings.EqualFold(existingOwner, owner) {
			continue
		}
		existingByName := make(map[string]string, len(existingDeclarations))
		for _, decl := range existingDeclarations {
			name := strings.TrimSpace(decl.ObjectAPIName)
			if name == "" {
				continue
			}
			existingByName[strings.ToLower(name)] = name
		}
		for _, decl := range declarations {
			name := strings.TrimSpace(decl.ObjectAPIName)
			if name == "" {
				continue
			}
			if existingName, ok := existingByName[strings.ToLower(name)]; ok {
				return errInvalidSearchAllDefaultFields(fmt.Sprintf("object %s is already declared by pack %s", existingName, strings.TrimSpace(existingOwner)))
			}
		}
	}
	return nil
}

type invalidSearchAllDefaultFieldsError struct {
	message string
}

func (e invalidSearchAllDefaultFieldsError) Error() string {
	return "invalid search_all_default_fields: " + e.message
}

func errInvalidSearchAllDefaultFields(message string) error {
	return invalidSearchAllDefaultFieldsError{message: message}
}
