package validation

import (
	"fmt"
	"strconv"
	"strings"
	"time"

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

// SchemaValidator enforces strict schema compliance for input data
type SchemaValidator struct{}

// NewSchemaValidator creates a new validator
func NewSchemaValidator() *SchemaValidator {
	return &SchemaValidator{}
}

// ValidateSchemaCompliance checks if the input data strictly matches the schema definition.
// It rejects:
// 1. Unknown fields (Typos, Extraneous data)
// 2. Read-Only fields (Formulas, AutoNumbers, System Generated)
// 3. Type mismatches
func (v *SchemaValidator) ValidateSchemaCompliance(data models.SObject, schema *models.ObjectMetadata, isUpdate bool, allowSystemFields bool) error {
	// 1. Build a map of valid fields for O(1) lookup
	validFields := make(map[string]models.FieldMetadata)
	for _, f := range schema.Fields {
		validFields[f.APIName] = f
	}

	// 2. Iterate over input data
	for key, val := range data {
		fieldDef, exists := validFields[key]

		// Check 1: Unknown Field
		if !exists {
			// Suggestion logic could be added here (Levenshtein distance)
			// Ignore standard SObject attributes field and cross-cutting
			// fields validated elsewhere (e.g. RecordTypeId)
			if key == "attributes" || key == "RecordTypeId" {
				continue
			}
			return errors.NewValidationError(key, fmt.Sprintf("Field '%s' does not exist on object '%s'. Please check for typos.", key, schema.APIName))
		}

		// Check 2: Read-Only Compliance
		// Intrinsically read-only fields (Formula, AutoNumber, RollupSummary) are never
		// writable regardless of permissions, matching Salesforce behavior.
		normalizedType := constants.NormalizeFieldType(string(fieldDef.Type))

		if fieldDef.Formula != nil && *fieldDef.Formula != "" {
			return errors.NewValidationError(key, fmt.Sprintf("Field '%s' is a computed formula and cannot be written to.", key))
		}
		if normalizedType == constants.FieldTypeAutonumber {
			return errors.NewValidationError(key, fmt.Sprintf("Field '%s' is an AutoNumber and cannot be set.", key))
		}
		if normalizedType == constants.FieldTypeRollupSummary {
			return errors.NewValidationError(key, fmt.Sprintf("Field '%s' is a RollupSummary and cannot be written to.", key))
		}

		// System Generated Fields (strict enforcement)
		// e.g., standard audit fields if managed by system
		// We allow writing audit fields ONLY if "AuditFields" permission enabled (not handled here yet),
		// but generally API shouldn't touch LastModifiedDate etc.
		if isSystemAuditField(key) && !allowSystemFields {
			// For now, reject. Use system-level overrides if needed (those bypass this validator or strip fields before).
			// If the user sends "created_date", we should probably Reject it to prevent spoofing,
			// unless we decide to just ignore it. Validating it as Error is "Harder to make mistake".
			return errors.NewValidationError(key, fmt.Sprintf("Field '%s' is a system audit field and cannot be set explicitly.", key))
		}

		// Check 3: Type Compliance
		if err := validateType(key, val, normalizedType); err != nil {
			return err
		}
		if err := validatePicklistOption(key, val, normalizedType, fieldDef); err != nil {
			return err
		}

		// Check 4: Numeric Constraints (Precision, Scale, Min/Max)
		if err := validateNumericConstraint(key, val, fieldDef); err != nil {
			return err
		}
	}

	return nil
}

func isSystemAuditField(key string) bool {
	switch key {
	case constants.FieldCreatedDate, constants.FieldLastModifiedDate, constants.FieldCreatedByID, constants.FieldLastModifiedByID, constants.FieldIsDeleted:
		return true
	}
	return false
}

func validateType(key string, val interface{}, fieldType constants.SchemaFieldType) error {
	if val == nil {
		return nil // Null is usually valid unless Required (Checked elsewhere)
	}

	// Check if it's a string-based type
	if isStringType(fieldType) {
		if _, ok := val.(string); !ok {
			return errors.NewValidationError(key, fmt.Sprintf("Field '%s' expects a String value, got %T", key, val))
		}
		return nil
	}

	switch fieldType {
	case constants.FieldTypeBoolean:
		switch v := val.(type) {
		case bool:
			// native bool — always valid
		case float64:
			// JSON unmarshals numeric 0/1 as float64
			if v != 0 && v != 1 {
				return errors.NewValidationError(key, fmt.Sprintf("Field '%s' expects a Boolean value (0 or 1), got %v", key, v))
			}
		case string:
			if _, err := strconv.ParseBool(v); err != nil {
				return errors.NewValidationError(key, fmt.Sprintf("Field '%s' expects a Boolean value, got string '%s'", key, v))
			}
		default:
			return errors.NewValidationError(key, fmt.Sprintf("Field '%s' expects a Boolean value, got %T", key, val))
		}
	case constants.FieldTypeDouble, constants.FieldTypeCurrency, constants.FieldTypePercent:
		// JSON numbers often come as float64
		switch val.(type) {
		case float64, float32, int, int64, int32:
			return nil
		default:
			return errors.NewValidationError(key, fmt.Sprintf("Field '%s' expects a Number value, got %T", key, val))
		}

	case constants.FieldTypeDate, constants.FieldTypeDatetime:
		// Expect String (ISO8601) or time.Time
		if _, ok := val.(time.Time); ok {
			return nil
		}
		if strVal, ok := val.(string); ok {
			// Simple check if it looks like a date/time?
			// Real validation happens in parsing, but strict type check passes for string.
			_ = strVal
			return nil
		}
		return errors.NewValidationError(key, fmt.Sprintf("Field '%s' expects a Date/Time string (ISO8601), got %T", key, val))
	}
	return nil
}

// isStringType checks if the field type stores string data
func isStringType(t constants.SchemaFieldType) bool {
	switch t {
	case constants.FieldTypeString,
		constants.FieldTypeTextarea,
		constants.FieldTypeRichtextarea,
		constants.FieldTypeEmail,
		constants.FieldTypePhone,
		constants.FieldTypePicklist,
		constants.FieldTypeUrl,
		constants.FieldTypeMasterDetail,
		constants.FieldTypeReference,
		constants.FieldTypeEncryptedString:
		return true
	}
	return false
}

func validatePicklistOption(key string, val interface{}, fieldType constants.SchemaFieldType, fieldDef models.FieldMetadata) error {
	if fieldType != constants.FieldTypePicklist || val == nil || len(fieldDef.Options) == 0 {
		return nil
	}

	strVal, ok := val.(string)
	if !ok || strVal == "" {
		return nil
	}

	for _, opt := range fieldDef.Options {
		if opt.Value == strVal {
			return nil
		}
	}
	return errors.NewValidationError(key, fmt.Sprintf("Field '%s' value %q is not an active picklist option.", key, strVal))
}

// validateNumericConstraint checks Precision, Scale, MinValue, MaxValue
func validateNumericConstraint(key string, val interface{}, field models.FieldMetadata) error {
	// Normalize the type for comparison
	normalizedType := constants.NormalizeFieldType(string(field.Type))

	// Only apply to numeric types
	if normalizedType != constants.FieldTypeDouble &&
		normalizedType != constants.FieldTypeCurrency &&
		normalizedType != constants.FieldTypePercent {
		return nil
	}

	var floatVal float64
	switch v := val.(type) {
	case float64:
		floatVal = v
	case float32:
		floatVal = float64(v)
	case int:
		floatVal = float64(v)
	case int64:
		floatVal = float64(v)
	case int32:
		floatVal = float64(v)
	default:
		return nil // Should be caught by validateType
	}

	// 1. Min/Max Value
	if field.MinValue != nil {
		if floatVal < *field.MinValue {
			return errors.NewValidationError(key, fmt.Sprintf("Field '%s' value %v is less than minimum allowed value %v", key, floatVal, *field.MinValue))
		}
	}
	if field.MaxValue != nil {
		if floatVal > *field.MaxValue {
			return errors.NewValidationError(key, fmt.Sprintf("Field '%s' value %v is greater than maximum allowed value %v", key, floatVal, *field.MaxValue))
		}
	}

	// 2. Precision and Scale
	if field.Precision != nil || field.Scale != nil {
		// Use string analysis to avoid floating point issues
		// 'f' format forces decimal notation (no scientific notation)
		// -1 precision means "necessary digits to represent the value uniquely"
		strVal := strconv.FormatFloat(floatVal, 'f', -1, 64)

		parts := strings.Split(strVal, ".")
		intPart := parts[0]
		// Handle negative sign
		intPart = strings.TrimPrefix(intPart, "-")

		fracPart := ""
		if len(parts) > 1 {
			fracPart = parts[1]
		}

		if field.Scale != nil {
			scale := *field.Scale
			if len(fracPart) > scale {
				return errors.NewValidationError(key, fmt.Sprintf("Field '%s' exceeds maximum scale of %d decimal places", key, scale))
			}
		}

		if field.Precision != nil {
			precision := *field.Precision
			scale := 0
			if field.Scale != nil {
				scale = *field.Scale
			}

			// Strict Definition: Precision is TOTAL significant digits (int + frac).
			// But traditionally in databases:
			// DECIMAL(10, 2) means 10 total, 2 reserved for decimal.
			// Max Integer Digits = Precision - Scale = 8.
			// 12345678.99 (8 int, 2 frac) -> OK.
			// 123456789.0 (9 int) -> Fail.

			maxIntDigits := precision - scale
			if len(intPart) > maxIntDigits {
				return errors.NewValidationError(key, fmt.Sprintf("Field '%s' exceeds maximum precision (integer part too large). Max %d digits allowed.", key, maxIntDigits))
			}
		}
	}

	return nil
}
