package records

import (
	"context"
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"strings"

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

type siblingMaxRepository interface {
	MaxNumericFieldForParent(ctx context.Context, schema *models.ObjectMetadata, targetField, parentField string, parentValue interface{}) (float64, error)
}

func (ps *PersistenceService) applySiblingMaxDefaults(ctx context.Context, objectName string, data models.SObject, schema *models.ObjectMetadata) ([]string, error) {
	if ps == nil || schema == nil || data == nil {
		return nil, nil
	}
	defaultedFields := []string{}
	for i := range schema.Fields {
		field := &schema.Fields[i]
		parentFieldName := siblingMaxParentFieldName(field)
		if parentFieldName == "" {
			continue
		}
		if err := validateSiblingMaxRuntimeConfig(schema, field, parentFieldName); err != nil {
			return nil, err
		}
		if siblingMaxFieldPresent(data, field) {
			continue
		}
		next, err := ps.nextSiblingMaxDefault(ctx, schema, field, parentFieldName, data[parentFieldName])
		if err != nil {
			return nil, err
		}
		data[field.APIName] = next
		defaultedFields = append(defaultedFields, field.APIName)
	}
	return defaultedFields, nil
}

func collectSiblingMaxParentLockIntents(schema *models.ObjectMetadata, data models.SObject) ([]recordlocks.ParentKey, error) {
	if schema == nil || data == nil {
		return nil, nil
	}
	var keys []recordlocks.ParentKey
	for i := range schema.Fields {
		field := &schema.Fields[i]
		parentFieldName := siblingMaxParentFieldName(field)
		if parentFieldName == "" {
			continue
		}
		if err := validateSiblingMaxRuntimeConfig(schema, field, parentFieldName); err != nil {
			return nil, err
		}
		if siblingMaxFieldPresent(data, field) {
			continue
		}
		key, ok, err := siblingMaxParentLockIntent(schema, parentFieldName, data[parentFieldName])
		if err != nil {
			return nil, err
		}
		if ok {
			keys = append(keys, key)
		}
	}
	return keys, nil
}

func ValidateSiblingMaxDefaultConfig(schema *models.ObjectMetadata, field *models.FieldMetadata) error {
	parentFieldName := siblingMaxParentFieldName(field)
	if parentFieldName == "" {
		return nil
	}
	return validateSiblingMaxRuntimeConfig(schema, field, parentFieldName)
}

func (ps *PersistenceService) PreviewSiblingMaxDefault(ctx context.Context, objectName, targetField, parentField string, parentValue interface{}) (int64, error) {
	if ps == nil || ps.metadata == nil {
		return 0, appErrors.NewInternalError("sibling-max default resolver is not configured", nil)
	}
	schema := ps.metadata.GetSchema(ctx, objectName)
	if schema == nil {
		return 0, appErrors.NewValidationError("object", fmt.Sprintf("object %s not found", objectName))
	}
	field := models.FindField(schema, targetField)
	if field == nil {
		return 0, appErrors.NewValidationError("field", fmt.Sprintf("field %s.%s not found", objectName, targetField))
	}
	configuredParent := siblingMaxParentFieldName(field)
	if configuredParent == "" || !strings.EqualFold(configuredParent, parentField) {
		return 0, appErrors.NewValidationError("siblingMaxParentField", fmt.Sprintf("field %s.%s is not configured for sibling-max defaulting by %s", objectName, targetField, parentField))
	}
	return ps.nextSiblingMaxDefault(ctx, schema, field, configuredParent, parentValue)
}

func (ps *PersistenceService) ValidateSiblingMaxDefaultParentForCreate(ctx context.Context, objectName, parentField string, parentValue interface{}, currentUser *models.UserSession) error {
	if ps == nil || ps.metadata == nil {
		return appErrors.NewInternalError("sibling-max default resolver is not configured", nil)
	}
	schema := ps.metadata.GetSchema(ctx, objectName)
	if schema == nil {
		return appErrors.NewValidationError("object", fmt.Sprintf("object %s not found", objectName))
	}
	parentField = strings.TrimSpace(parentField)
	if parentField == "" {
		return appErrors.NewValidationError("siblingMaxParentField", "sibling-max default parent field is required")
	}
	if isBlankSiblingMaxParentValue(parentValue) {
		return appErrors.NewFieldIntegrityError(
			fmt.Sprintf("%s is required for sibling-max defaulting", parentField),
			parentField,
		)
	}
	field := models.FindField(schema, parentField)
	if field == nil {
		return appErrors.NewValidationError("siblingMaxParentField", fmt.Sprintf("parent field %s.%s not found", objectName, parentField))
	}
	if !isNonPolymorphicReference(field) {
		return appErrors.NewValidationError("siblingMaxParentField", fmt.Sprintf("parent field %s.%s must be a non-polymorphic reference or master-detail field", objectName, parentField))
	}
	return ps.validateNonPolymorphicLookups(ctx, models.SObject{field.APIName: parentValue}, schema, currentUser)
}

func (ps *PersistenceService) nextSiblingMaxDefault(ctx context.Context, schema *models.ObjectMetadata, field *models.FieldMetadata, parentFieldName string, parentValue interface{}) (int64, error) {
	if err := validateSiblingMaxRuntimeConfig(schema, field, parentFieldName); err != nil {
		return 0, err
	}
	if isBlankSiblingMaxParentValue(parentValue) {
		return 0, appErrors.NewFieldIntegrityError(
			fmt.Sprintf("%s requires %s for sibling-max defaulting", field.APIName, parentFieldName),
			field.APIName,
			parentFieldName,
		)
	}
	repo, ok := ps.repo.(siblingMaxRepository)
	if !ok || repo == nil {
		return 0, appErrors.NewInternalError("sibling-max default repository is not configured", nil)
	}
	if key, ok, err := siblingMaxParentLockIntent(schema, parentFieldName, parentValue); err != nil {
		return 0, err
	} else if ok && recordlocks.ScopeFromContext(ctx) != nil {
		if _, err := ps.LockParentRows(ctx, []recordlocks.ParentKey{key}); err != nil {
			return 0, err
		}
	}
	maxValue, err := repo.MaxNumericFieldForParent(ctx, schema, field.APIName, parentFieldName, parentValue)
	if err != nil {
		return 0, appErrors.NewInternalError("failed to resolve sibling-max default", err)
	}
	maxValue, err = overlayPreviewSiblingMaxRecords(ctx, schema, field, parentFieldName, parentValue, maxValue)
	if err != nil {
		return 0, err
	}
	return siblingMaxNextInteger(field.APIName, maxValue)
}

// previewVisibleSiblingMaxDefault resolves the non-locking UI preview through
// the canonical user query path. Unlike the authoritative insert calculation,
// the preview must not include sibling rows the caller cannot read.
func (ps *PersistenceService) previewVisibleSiblingMaxDefault(
	ctx context.Context,
	schema *models.ObjectMetadata,
	field *models.FieldMetadata,
	parentFieldName string,
	parentValue interface{},
	currentUser *models.UserSession,
) (int64, error) {
	if err := validateSiblingMaxRuntimeConfig(schema, field, parentFieldName); err != nil {
		return 0, err
	}
	if isBlankSiblingMaxParentValue(parentValue) {
		return 0, appErrors.NewFieldIntegrityError(
			fmt.Sprintf("%s requires %s for sibling-max defaulting", field.APIName, parentFieldName),
			field.APIName,
		)
	}
	if ps.querySvc == nil {
		return 0, appErrors.NewInternalError("sibling-max preview query service is not configured", nil)
	}

	targetField := field.APIName
	value, err := ps.querySvc.RunAnalytics(ctx, models.AnalyticsQuery{
		ObjectAPIName: schema.APIName,
		Operation:     "max",
		Field:         &targetField,
		Criteria: []models.QueryCriterion{{
			Field: parentFieldName,
			Op:    "=",
			Val:   parentValue,
		}},
	}, currentUser)
	if err != nil {
		return 0, err
	}

	maxValue := float64(0)
	if value != nil {
		maxValue, err = siblingMaxPreviewNumericValue(field.APIName, value)
		if err != nil {
			return 0, err
		}
	}
	maxValue, err = overlayPreviewSiblingMaxRecords(ctx, schema, field, parentFieldName, parentValue, maxValue)
	if err != nil {
		return 0, err
	}
	return siblingMaxNextInteger(field.APIName, maxValue)
}

func siblingMaxNextInteger(fieldAPIName string, maxValue float64) (int64, error) {
	if err := validateSiblingMaxWholeFinite(fieldAPIName, maxValue); err != nil {
		return 0, err
	}
	return int64(maxValue) + 1, nil
}

func overlayPreviewSiblingMaxRecords(
	ctx context.Context,
	schema *models.ObjectMetadata,
	field *models.FieldMetadata,
	parentFieldName string,
	parentValue interface{},
	maxValue float64,
) (float64, error) {
	if !constants.HasInsertPreviewPreparation(ctx) || schema == nil || field == nil {
		return maxValue, nil
	}
	records := constants.PreviewSyntheticRecordsForObject(ctx, schema.APIName)
	if len(records) == 0 {
		return maxValue, nil
	}

	parentField := models.FindField(schema, parentFieldName)
	parentKey := siblingMaxPreviewReferenceKey(parentValue)
	for _, record := range records {
		fields := record.Fields
		if siblingMaxPreviewRecordDeleted(fields) {
			continue
		}
		syntheticParent, ok := siblingMaxPreviewFieldValue(fields, parentField, parentFieldName)
		if !ok || siblingMaxPreviewReferenceKey(syntheticParent) != parentKey {
			continue
		}
		rawValue, ok := siblingMaxPreviewFieldValue(fields, field, field.APIName)
		if !ok || rawValue == nil {
			continue
		}
		numericValue, err := siblingMaxPreviewNumericValue(field.APIName, rawValue)
		if err != nil {
			return 0, err
		}
		if numericValue > maxValue {
			maxValue = numericValue
		}
	}
	return maxValue, nil
}

func siblingMaxPreviewFieldValue(fields map[string]interface{}, field *models.FieldMetadata, fallbackAPIName string) (interface{}, bool) {
	if fields == nil {
		return nil, false
	}
	if field != nil {
		if value, ok := fields[field.APIName]; ok {
			return value, true
		}
		if field.PhysicalName != "" && !strings.EqualFold(field.PhysicalName, field.APIName) {
			if value, ok := fields[field.PhysicalName]; ok {
				return value, true
			}
		}
	}
	if fallbackAPIName != "" {
		value, ok := fields[fallbackAPIName]
		return value, ok
	}
	return nil, false
}

func siblingMaxPreviewReferenceKey(value interface{}) string {
	if value == nil {
		return ""
	}
	return strings.TrimSpace(fmt.Sprintf("%v", value))
}

func siblingMaxPreviewRecordDeleted(fields map[string]interface{}) bool {
	value, ok := fields[constants.FieldIsDeleted]
	if !ok || value == nil {
		return false
	}
	switch v := value.(type) {
	case bool:
		return v
	case int:
		return v != 0
	case int8:
		return v != 0
	case int16:
		return v != 0
	case int32:
		return v != 0
	case int64:
		return v != 0
	case uint:
		return v != 0
	case uint8:
		return v != 0
	case uint16:
		return v != 0
	case uint32:
		return v != 0
	case uint64:
		return v != 0
	case float32:
		return v != 0
	case float64:
		return v != 0
	case string:
		s := strings.TrimSpace(v)
		if parsed, err := strconv.ParseBool(s); err == nil {
			return parsed
		}
		if parsed, err := strconv.ParseFloat(s, 64); err == nil {
			return parsed != 0
		}
	}
	return false
}

func siblingMaxPreviewNumericValue(fieldAPIName string, value interface{}) (float64, error) {
	var numericValue float64
	switch v := value.(type) {
	case int:
		numericValue = float64(v)
	case int8:
		numericValue = float64(v)
	case int16:
		numericValue = float64(v)
	case int32:
		numericValue = float64(v)
	case int64:
		numericValue = float64(v)
	case uint:
		numericValue = float64(v)
	case uint8:
		numericValue = float64(v)
	case uint16:
		numericValue = float64(v)
	case uint32:
		numericValue = float64(v)
	case uint64:
		numericValue = float64(v)
	case float32:
		numericValue = float64(v)
	case float64:
		numericValue = v
	case json.Number:
		parsed, err := v.Float64()
		if err != nil {
			return 0, siblingMaxInvalidNumericError(fieldAPIName)
		}
		numericValue = parsed
	case string:
		parsed, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
		if err != nil {
			return 0, siblingMaxInvalidNumericError(fieldAPIName)
		}
		numericValue = parsed
	default:
		return 0, siblingMaxInvalidNumericError(fieldAPIName)
	}
	if err := validateSiblingMaxWholeFinite(fieldAPIName, numericValue); err != nil {
		return 0, err
	}
	return numericValue, nil
}

func validateSiblingMaxWholeFinite(fieldAPIName string, value float64) error {
	if math.IsNaN(value) || math.IsInf(value, 0) || math.Trunc(value) != value {
		return siblingMaxInvalidNumericError(fieldAPIName)
	}
	return nil
}

func siblingMaxInvalidNumericError(fieldAPIName string) error {
	return appErrors.NewFieldIntegrityError(
		fmt.Sprintf("%s has invalid sibling-max default data: existing sibling values must be whole finite numbers", fieldAPIName),
		fieldAPIName,
	)
}

func rejectOmittedSiblingMaxDefaultsForBulk(record models.SObject, schema *models.ObjectMetadata) error {
	if schema == nil || record == nil {
		return nil
	}
	for i := range schema.Fields {
		field := &schema.Fields[i]
		parentFieldName := siblingMaxParentFieldName(field)
		if parentFieldName == "" {
			continue
		}
		if err := validateSiblingMaxRuntimeConfig(schema, field, parentFieldName); err != nil {
			return err
		}
		if siblingMaxFieldPresent(record, field) {
			continue
		}
		return appErrors.NewFieldIntegrityError(
			fmt.Sprintf("bulk create does not support omitted sibling-max default field %s; supply an explicit value or use single-record create", field.APIName),
			field.APIName,
			parentFieldName,
		)
	}
	return nil
}

func rejectInvalidSiblingMaxFinalImageForBulk(record models.SObject, schema *models.ObjectMetadata) error {
	if schema == nil || record == nil {
		return nil
	}
	for i := range schema.Fields {
		field := &schema.Fields[i]
		parentFieldName := siblingMaxParentFieldName(field)
		if parentFieldName == "" {
			continue
		}
		if err := validateSiblingMaxRuntimeConfig(schema, field, parentFieldName); err != nil {
			return err
		}
		value, ok := siblingMaxFieldValue(record, field)
		if !ok {
			return appErrors.NewFieldIntegrityError(
				fmt.Sprintf("bulk create final image omitted sibling-max default field %s after before-save automation", field.APIName),
				field.APIName,
				parentFieldName,
			)
		}
		if _, err := siblingMaxPreviewNumericValue(field.APIName, value); err != nil {
			return appErrors.NewFieldIntegrityError(
				fmt.Sprintf("bulk create final image has invalid sibling-max default field %s: value must be a whole finite number", field.APIName),
				field.APIName,
				parentFieldName,
			)
		}
	}
	return nil
}

func schemaHasSiblingMaxDefaults(schema *models.ObjectMetadata) bool {
	if schema == nil {
		return false
	}
	for i := range schema.Fields {
		if siblingMaxParentFieldName(&schema.Fields[i]) != "" {
			return true
		}
	}
	return false
}

func validateSiblingMaxRuntimeConfig(schema *models.ObjectMetadata, field *models.FieldMetadata, parentFieldName string) error {
	if schema == nil || field == nil {
		return appErrors.NewInternalError("invalid sibling-max default metadata", nil)
	}
	if field.DefaultValue != nil {
		return appErrors.NewFieldIntegrityError(
			fmt.Sprintf("%s has invalid sibling-max default metadata: cannot be combined with defaultValue", field.APIName),
			field.APIName,
		)
	}
	if field.Formula != nil && strings.TrimSpace(*field.Formula) != "" {
		return appErrors.NewFieldIntegrityError(
			fmt.Sprintf("%s has invalid sibling-max default metadata: target field must be writable", field.APIName),
			field.APIName,
		)
	}
	if field.RollupConfig != nil ||
		constants.NormalizeFieldType(string(field.Type)) == constants.FieldTypeFormula ||
		constants.NormalizeFieldType(string(field.Type)) == constants.FieldTypeRollupSummary ||
		constants.NormalizeFieldType(string(field.Type)) == constants.FieldTypeAutonumber {
		return appErrors.NewFieldIntegrityError(
			fmt.Sprintf("%s has invalid sibling-max default metadata: target field must be writable", field.APIName),
			field.APIName,
		)
	}
	if !siblingMaxRuntimeTargetWriteable(field) {
		return appErrors.NewFieldIntegrityError(
			fmt.Sprintf("%s has invalid sibling-max default metadata: target field must be createable and updateable", field.APIName),
			field.APIName,
		)
	}
	if !siblingMaxRuntimeTargetTypeAllowed(field) {
		return appErrors.NewFieldIntegrityError(
			fmt.Sprintf("%s has invalid sibling-max default metadata: target field must be integer-compatible numeric", field.APIName),
			field.APIName,
		)
	}
	if strings.EqualFold(field.APIName, parentFieldName) {
		return appErrors.NewFieldIntegrityError(
			fmt.Sprintf("%s has invalid sibling-max default metadata: parent field cannot be the target field", field.APIName),
			field.APIName,
			parentFieldName,
		)
	}
	parentField := models.FindField(schema, parentFieldName)
	if parentField == nil {
		return appErrors.NewFieldIntegrityError(
			fmt.Sprintf("%s has invalid sibling-max default metadata: parent field %s was not found", field.APIName, parentFieldName),
			field.APIName,
			parentFieldName,
		)
	}
	if !siblingMaxRuntimeParentFieldAllowed(parentField) {
		return appErrors.NewFieldIntegrityError(
			fmt.Sprintf("%s has invalid sibling-max default metadata: parent field %s must be a persisted non-polymorphic reference or master-detail field", field.APIName, parentFieldName),
			field.APIName,
			parentFieldName,
		)
	}
	return nil
}

func siblingMaxRuntimeTargetWriteable(field *models.FieldMetadata) bool {
	if field == nil {
		return false
	}
	// Runtime receives already-compiled capabilities from the immutable Catalog
	// view. Re-running intrinsic classification here would require the L1 pack
	// manifest in the L0 process and would misclassify valid standard fields as
	// discovered. The generic write-protection policy remains safe to apply as a
	// fail-closed check because it never depends on product metadata.
	createable, updateable := fieldcapabilities.ApplyWriteProtectionPolicy(field.Createable, field.Updateable, field.WriteProtectionPolicy)
	return createable && updateable
}

func siblingMaxRuntimeParentFieldAllowed(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 siblingMaxRuntimeTargetTypeAllowed(field *models.FieldMetadata) bool {
	if field == nil {
		return false
	}
	switch constants.NormalizeFieldType(string(field.Type)) {
	case "integer", "int":
		return true
	default:
		return false
	}
}

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

func siblingMaxParentLockIntent(schema *models.ObjectMetadata, parentFieldName string, parentValue interface{}) (recordlocks.ParentKey, bool, error) {
	field := models.FindField(schema, parentFieldName)
	if field == nil || len(field.ReferenceTo) != 1 {
		return recordlocks.ParentKey{}, false, nil
	}
	parentObject := strings.TrimSpace(field.ReferenceTo[0])
	if parentObject == "" {
		return recordlocks.ParentKey{}, false, nil
	}
	parentID, ok := parentValue.(string)
	if !ok {
		return recordlocks.ParentKey{}, false, nil
	}
	parentID = strings.TrimSpace(parentID)
	if parentID == "" {
		return recordlocks.ParentKey{}, false, nil
	}
	return recordlocks.ParentKey{ObjectName: parentObject, RecordID: parentID}, true, nil
}

func siblingMaxFieldPresent(data models.SObject, field *models.FieldMetadata) bool {
	_, ok := siblingMaxFieldValue(data, field)
	return ok
}

func siblingMaxFieldValue(data models.SObject, field *models.FieldMetadata) (interface{}, bool) {
	if data == nil || field == nil {
		return nil, false
	}
	if value, ok := data[field.APIName]; ok {
		return value, true
	}
	if field.PhysicalName != "" && !strings.EqualFold(field.PhysicalName, field.APIName) {
		value, ok := data[field.PhysicalName]
		return value, ok
	}
	return nil, false
}

func isBlankSiblingMaxParentValue(value interface{}) bool {
	if value == nil {
		return true
	}
	if s, ok := value.(string); ok {
		return strings.TrimSpace(s) == ""
	}
	return false
}
