package metadata

import (
	"context"
	"fmt"
	"log"
	"time"

	domainSchema "github.com/nexuscrm/backend/internal/domain/schema"
	apperrors "github.com/nexuscrm/backend/pkg/errors"
	sharedEvents "github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

// UpdateField updates an existing field
func (ms *MetadataService) UpdateField(ctx context.Context, objectAPIName, fieldAPIName string, updates *models.FieldMetadata) error {
	ms.mu.Lock()
	defer ms.mu.Unlock()

	// Get the object
	obj, err := ms.repo.GetSchemaByAPIName(ctx, objectAPIName)
	if err != nil || obj == nil {
		return apperrors.NewNotFoundError("Object", objectAPIName)
	}

	// Find the field
	var existingField *models.FieldMetadata
	for i := range obj.Fields {
		if obj.Fields[i].APIName == fieldAPIName {
			existingField = &obj.Fields[i]
			break
		}
	}
	if existingField == nil {
		return apperrors.NewNotFoundError("Field", fmt.Sprintf("%s.%s", objectAPIName, fieldAPIName))
	}

	// Don't allow editing system fields (except help text and default value which is safe)
	if existingField.IsSystem {
		if (updates.Type != "" && updates.Type != existingField.Type) ||
			(updates.Required != existingField.Required) ||
			(updates.IsUnique != existingField.IsUnique) {
			return apperrors.NewValidationError(fieldAPIName, "cannot modify structural properties of system field")
		}
	}

	// Apply updates
	if updates.Label != "" {
		existingField.Label = updates.Label
	}
	existingField.Required = updates.Required
	existingField.IsUnique = updates.IsUnique

	if updates.HelpText != nil {
		existingField.HelpText = updates.HelpText
	}
	if updates.DefaultValue != nil {
		existingField.DefaultValue = updates.DefaultValue
	}
	if updates.Options != nil {
		existingField.Options = updates.Options
	}
	if updates.MinLength != nil {
		existingField.MinLength = updates.MinLength
	}
	if updates.MaxLength != nil {
		existingField.MaxLength = updates.MaxLength
	}
	if updates.ReferenceTo != nil {
		existingField.ReferenceTo = updates.ReferenceTo
	}
	if updates.Formula != nil {
		existingField.Formula = updates.Formula
	}
	if updates.ReturnType != nil {
		existingField.ReturnType = updates.ReturnType
	}

	// Handle Type Changes (for non-system fields only)
	if updates.Type != "" && updates.Type != existingField.Type {
		log.Printf("🔧 Field type change detected: %s.%s from %s to %s", objectAPIName, fieldAPIName, existingField.Type, updates.Type)

		// Build column definition for ALTER TABLE
		colDef := domainSchema.ColumnDefinition{
			Name:        fieldAPIName,
			Type:        ms.schemaMgr.MapFieldTypeToSQL(string(updates.Type)),
			LogicalType: string(updates.Type),
			Nullable:    !existingField.Required,
		}

		if err := ms.schemaMgr.ModifyColumn(objectAPIName, fieldAPIName, colDef); err != nil {
			return apperrors.NewInternalError("failed to modify field type", err)
		}

		existingField.Type = updates.Type
	}

	// Revalidate after merge so field-type-specific invariants (e.g. the
	// multi-picklist no-';' option-value constraint — #1686) apply to both
	// create and update paths. UpdateField was previously unguarded and would
	// silently accept option values that reopen the #1683 self-update loop.
	// The nil guard mirrors the existing convention in field_create.go +
	// field_batch.go (validationSvc is optional in some test wirings).
	if ms.validationSvc != nil {
		if err := ms.validationSvc.ValidateFieldMetadata(existingField); err != nil {
			return err
		}
	}

	// Generate Field ID (reuse existing)
	fieldID := GenerateFieldID(obj.APIName, fieldAPIName)

	// Delegate to SchemaManager
	if err := ms.schemaMgr.SaveFieldMetadataWithIDs(ctx, existingField, obj.ID, obj.APIName, fieldID, nil); err != nil {
		return apperrors.NewInternalError("failed to update field metadata", err)
	}

	ms.invalidateCacheLocked()

	// Publish Event
	ms.publishMetadataEvent(ctx, sharedEvents.FieldUpdated, sharedEvents.SchemaEventPayload{
		EventType:     sharedEvents.FieldUpdated,
		ObjectAPIName: objectAPIName,
		FieldAPIName:  fieldAPIName,
		Timestamp:     time.Now().Format(time.RFC3339),
	})

	return nil
}
