package metadata

import (
	"context"
	"fmt"
	"strings"

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

// --- Validation Rule CRUD ---

// CreateValidationRule creates a new validation rule
func (ms *MetadataService) CreateValidationRule(ctx context.Context, rule *models.ValidationRule) error {
	ms.mu.Lock()
	defer ms.mu.Unlock()

	// Validate
	rule.ObjectAPIName = strings.ToLower(rule.ObjectAPIName)
	if rule.ObjectAPIName == "" {
		return apperrors.NewValidationError("object_api_name", "object API name is required")
	}
	if rule.Name == "" {
		return apperrors.NewValidationError("name", "rule name is required")
	}
	if rule.Condition == "" {
		return apperrors.NewValidationError("condition", "error condition formula is required")
	}
	if rule.ErrorMessage == "" {
		return apperrors.NewValidationError("error_message", "error message is required")
	}

	if rule.Description != nil {
		trimmed := strings.TrimSpace(*rule.Description)
		if trimmed == "" {
			rule.Description = nil
		} else {
			rule.Description = &trimmed
		}
	}
	if rule.ErrorDisplayField != nil {
		trimmed := strings.TrimSpace(*rule.ErrorDisplayField)
		if trimmed == "" {
			rule.ErrorDisplayField = nil
		} else {
			rule.ErrorDisplayField = &trimmed
		}
	}

	if rule.ID == "" {
		rule.ID = GenerateID()
	}
	rule.IsCustomized = true
	if err := ms.validateValidationRuleRecordSetBindings(ctx, rule); err != nil {
		return err
	}

	// Check for duplicate (object_api_name, name) - this matches the unique constraint
	exists, err := ms.repo.CheckValidationRuleExists(ctx, rule.ObjectAPIName, rule.Name)
	if err != nil {
		return apperrors.NewInternalError("failed to check for existing validation rule", err)
	}
	if exists {
		return apperrors.NewConflictError("ValidationRule", constants.FieldName, rule.Name)
	}

	if err := ms.repo.CreateValidationRule(ctx, rule); err != nil {
		return apperrors.NewInternalError("failed to insert validation rule", err)
	}

	// Invalidate cache
	ms.invalidateCacheLocked()
	ms.invalidateValidationProjectionLocked(rule.ObjectAPIName)
	return nil
}

// UpdateValidationRule updates an existing validation rule
func (ms *MetadataService) UpdateValidationRule(ctx context.Context, id string, updates *models.ValidationRule) error {
	ms.mu.Lock()
	defer ms.mu.Unlock()

	// Find the rule in DB
	existingRule, err := ms.repo.GetValidationRule(ctx, id)
	if err != nil {
		return apperrors.NewInternalError("failed to get validation rule", err)
	}
	if existingRule == nil {
		return apperrors.NewNotFoundError("ValidationRule", id)
	}

	// If name is changing, check for conflicts
	if updates.Name != "" && updates.Name != existingRule.Name {
		exists, err := ms.repo.CheckValidationRuleExists(ctx, existingRule.ObjectAPIName, updates.Name)
		if err != nil {
			return apperrors.NewInternalError("failed to check for existing validation rule", err)
		}
		if exists {
			return apperrors.NewConflictError("ValidationRule", constants.FieldName, updates.Name)
		}
		existingRule.Name = updates.Name
	}

	// Active is boolean
	existingRule.Active = updates.Active
	existingRule.IsCustomized = true

	if updates.Condition != "" {
		existingRule.Condition = updates.Condition
	}
	if updates.ErrorMessage != "" {
		existingRule.ErrorMessage = updates.ErrorMessage
	}
	if updates.Description != nil {
		trimmed := strings.TrimSpace(*updates.Description)
		if trimmed == "" {
			existingRule.Description = nil
		} else {
			existingRule.Description = &trimmed
		}
	}
	if updates.ErrorDisplayField != nil {
		trimmed := strings.TrimSpace(*updates.ErrorDisplayField)
		if trimmed == "" {
			existingRule.ErrorDisplayField = nil
		} else {
			existingRule.ErrorDisplayField = &trimmed
		}
	}
	if updates.RecordSetBindings != nil {
		existingRule.RecordSetBindings = updates.RecordSetBindings
	}
	if err := ms.validateValidationRuleRecordSetBindings(ctx, existingRule); err != nil {
		return err
	}

	// Update DB
	if err := ms.repo.UpdateValidationRule(ctx, id, existingRule); err != nil {
		return apperrors.NewInternalError("failed to update validation rule", err)
	}

	// Invalidate cache
	ms.invalidateCacheLocked()
	ms.invalidateValidationProjectionLocked(existingRule.ObjectAPIName)
	return nil
}

func (ms *MetadataService) validateValidationRuleRecordSetBindings(ctx context.Context, rule *models.ValidationRule) error {
	if rule == nil || len(rule.RecordSetBindings) == 0 {
		return nil
	}
	if ms.validationSvc == nil {
		return nil
	}
	rootSchema := ms.GetSchema(ctx, rule.ObjectAPIName)
	if rootSchema == nil {
		return apperrors.NewValidationError("recordSetBindings", fmt.Sprintf("unknown validation rule object %q", rule.ObjectAPIName))
	}
	lookup := func(objectAPIName string) *models.ObjectMetadata {
		if strings.EqualFold(objectAPIName, "RecordType") {
			return recordTypeValidationPseudoSchema()
		}
		return ms.GetSchema(ctx, objectAPIName)
	}
	seen := map[string]struct{}{}
	for _, binding := range rule.RecordSetBindings {
		if err := ms.validationSvc.validateRecordSetBindingBasic(rootSchema, binding, seen); err != nil {
			return apperrors.NewValidationError("recordSetBindings", err.Error())
		}
		candidateSchema := lookup(binding.ObjectAPIName)
		if candidateSchema == nil {
			return apperrors.NewValidationError("recordSetBindings", fmt.Sprintf("recordSetBinding %q references unknown object %q", binding.Name, binding.ObjectAPIName))
		}
		if err := validateRecordSetBindingSchemas(rootSchema, candidateSchema, binding, lookup); err != nil {
			return apperrors.NewValidationError("recordSetBindings", err.Error())
		}
	}
	return nil
}

// DeleteValidationRule deletes a validation rule
func (ms *MetadataService) DeleteValidationRule(ctx context.Context, id string) error {
	ms.mu.Lock()
	defer ms.mu.Unlock()

	// Fetch before delete to get object name for projection invalidation
	existingRule, _ := ms.repo.GetValidationRule(ctx, id)

	if err := ms.repo.DeleteValidationRule(ctx, id); err != nil {
		return apperrors.NewInternalError("failed to delete validation rule", err)
	}

	// Invalidate cache
	ms.invalidateCacheLocked()
	if existingRule != nil {
		ms.invalidateValidationProjectionLocked(existingRule.ObjectAPIName)
	}
	return nil
}

// ValidateMaxMasterDetailFields checks if an object can accept another Master-Detail field
// Limit is 2 per object (Standard PaaS limit)
// Returns error if limit reached
func (ms *MetadataService) ValidateMaxMasterDetailFields(obj *models.ObjectMetadata, excludeField string) error {
	count := 0
	for _, f := range obj.Fields {
		// Build a count of EXISTING master-detail fields
		if f.IsMasterDetail {
			// If we are updating a field, don't count itself (if it was already MD)
			// But usually this logic is for "Add New" or "Change Type to MD"
			if excludeField != "" && strings.EqualFold(f.APIName, excludeField) {
				continue
			}
			count++
		}
	}

	if count >= 2 {
		return apperrors.NewValidationError("is_master_detail", fmt.Sprintf("Object '%s' already has the maximum of 2 Master-Detail relationships", obj.APIName))
	}

	return nil
}

func (ms *MetadataService) invalidateValidationProjectionLocked(objectAPIName string) {
	if ms.projectionInvalidator != nil && strings.TrimSpace(objectAPIName) != "" {
		ms.projectionInvalidator.InvalidateValidationProjection(objectAPIName)
	}
}
