package metadata

import (
	"fmt"
	"strings"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/fieldcapabilities"
	"github.com/nexuscrm/shared/pkg/models"
)

func siblingMaxParentFieldValue(field *models.FieldMetadata) string {
	if field == nil || field.SiblingMaxParentField == nil {
		return ""
	}
	return strings.TrimSpace(*field.SiblingMaxParentField)
}

func siblingMaxColumnParentField(field *models.FieldMetadata) string {
	return siblingMaxParentFieldValue(field)
}

func validateSiblingMaxFieldsForObject(schema *models.ObjectMetadata, fields []*models.FieldMetadata) error {
	if schema == nil {
		return nil
	}
	if len(fields) == 0 {
		for i := range schema.Fields {
			if err := validateSiblingMaxFieldForObject(schema, &schema.Fields[i]); err != nil {
				return err
			}
		}
		return nil
	}
	for _, field := range fields {
		if err := validateSiblingMaxFieldForObject(schema, field); err != nil {
			return err
		}
	}
	return nil
}

func validateSiblingMaxFieldForObject(schema *models.ObjectMetadata, field *models.FieldMetadata) error {
	if err := validateSiblingMaxMetadata(field); err != nil {
		return err
	}
	parentFieldName := siblingMaxParentFieldValue(field)
	if parentFieldName == "" {
		return nil
	}
	if !siblingMaxMetadataTargetWriteable(schema.APIName, field) {
		return apperrors.NewValidationError("siblingMaxParentField", "siblingMaxParentField requires a createable and updateable numeric target field")
	}
	parentField := models.FindField(schema, parentFieldName)
	if parentField == nil {
		return apperrors.NewValidationError(
			"siblingMaxParentField",
			fmt.Sprintf("%s.%s siblingMaxParentField references missing parent field %q", schema.APIName, field.APIName, parentFieldName),
		)
	}
	if !siblingMaxMetadataParentFieldAllowed(parentField) {
		return apperrors.NewValidationError(
			"siblingMaxParentField",
			fmt.Sprintf("%s.%s siblingMaxParentField parent %q must be a persisted non-polymorphic reference or master-detail field", schema.APIName, field.APIName, parentFieldName),
		)
	}
	return nil
}

func siblingMaxMetadataParentFieldAllowed(field *models.FieldMetadata) bool {
	if field == nil {
		return false
	}
	if !constants.IsReferenceType(constants.NormalizeFieldType(string(field.Type))) ||
		field.IsPolymorphic ||
		len(field.ReferenceTo) != 1 ||
		strings.TrimSpace(field.ReferenceTo[0]) == "" {
		return false
	}
	if field.IsVirtual ||
		field.ProjectionConfig != nil ||
		field.RollupConfig != nil ||
		strings.TrimSpace(field.GeneratedForPolymorphicReference) != "" {
		return false
	}
	if field.Formula != nil && strings.TrimSpace(*field.Formula) != "" {
		return false
	}
	return true
}

func siblingMaxMetadataTargetWriteable(objectAPIName string, field *models.FieldMetadata) bool {
	if field == nil || !field.Createable || !field.Updateable {
		return false
	}
	createable, updateable := fieldcapabilities.ComputeIntrinsic(objectAPIName, field)
	return createable && updateable
}

func objectWithFieldCandidate(obj *models.ObjectMetadata, candidate *models.FieldMetadata) *models.ObjectMetadata {
	if obj == nil || candidate == nil {
		return obj
	}
	proposed := *obj
	proposed.Fields = append([]models.FieldMetadata(nil), obj.Fields...)
	replaced := false
	for i := range proposed.Fields {
		if strings.EqualFold(proposed.Fields[i].APIName, candidate.APIName) {
			proposed.Fields[i] = *candidate
			replaced = true
			break
		}
	}
	if !replaced {
		proposed.Fields = append(proposed.Fields, *candidate)
	}
	return &proposed
}

func objectWithFieldCandidates(obj *models.ObjectMetadata, fields []*models.FieldMetadata) *models.ObjectMetadata {
	if obj == nil || len(fields) == 0 {
		return obj
	}
	proposed := *obj
	proposed.Fields = append([]models.FieldMetadata(nil), obj.Fields...)
	for _, field := range fields {
		if field == nil {
			continue
		}
		replaced := false
		for i := range proposed.Fields {
			if strings.EqualFold(proposed.Fields[i].APIName, field.APIName) {
				proposed.Fields[i] = *field
				replaced = true
				break
			}
		}
		if !replaced {
			proposed.Fields = append(proposed.Fields, *field)
		}
	}
	return &proposed
}
