package automation

import (
	"context"
	"fmt"
	"regexp"
	"strings"

	"github.com/nexuscrm/kernel/pkg/schemautil"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

type flowBinaryOperatorOptions struct {
	ctx                 context.Context
	fieldMeta           *models.FieldMetadata
	useStringEquality   bool
	useFieldSetEquality bool
}

var flowBinaryRuntimeOperators = []string{
	"EqualTo",
	"NotEqualTo",
	"GreaterThan",
	"GreaterThanOrEqualTo",
	"LessThan",
	"LessThanOrEqualTo",
	"Contains",
	"NotContains",
	"IsNull",
	"IsNotNull",
	"IsBlank",
	"IsNotBlank",
	"StartsWith",
	"DoesNotStartWith",
	"EndsWith",
	"DoesNotEndWith",
	"Between",
	"NotBetween",
	"In",
	"NotIn",
	"Includes",
	"Excludes",
	"EmptyString",
}

func isMultiPicklistFieldMeta(fieldMeta *models.FieldMetadata) bool {
	return fieldMeta != nil && constants.NormalizeFieldType(string(fieldMeta.Type)) == constants.FieldTypeMultiPicklist
}

// evaluateFlowBinaryOperator is the shared scalar operator table for Flow
// start filters and decision conditions. Callers own value resolution and the
// stateful IsChanged / pick-tag cases; this helper owns the common comparison
// semantics so ctx-aware and legacy paths do not drift operator-by-operator.
func (e *FlowExecutor) evaluateFlowBinaryOperator(operator string, leftValue, rightValue interface{}, opts flowBinaryOperatorOptions) (bool, bool) {
	var keyPrefixMatcher flowKeyPrefixMatcher
	if e != nil {
		keyPrefixMatcher = e.sfKeyPrefixMatch
	}
	return evaluateFlowBinaryOperatorCore(operator, leftValue, rightValue, opts, keyPrefixMatcher)
}

type flowKeyPrefixMatcher func(context.Context, interface{}, string) (bool, bool)

func evaluateFlowBinaryOperatorCore(operator string, leftValue, rightValue interface{}, opts flowBinaryOperatorOptions, keyPrefixMatcher flowKeyPrefixMatcher) (bool, bool) {
	switch normalizeFlowOperator(operator) {
	case "EqualTo":
		if opts.useStringEquality {
			return fmt.Sprintf("%v", leftValue) == fmt.Sprintf("%v", rightValue), true
		}
		if opts.useFieldSetEquality && isMultiPicklistFieldMeta(opts.fieldMeta) {
			return schemautil.CanonicalMultiPicklistSetKey(leftValue) == schemautil.CanonicalMultiPicklistSetKey(rightValue), true
		}
		return picklistAwareEqual(leftValue, rightValue), true
	case "NotEqualTo":
		if opts.useStringEquality {
			return fmt.Sprintf("%v", leftValue) != fmt.Sprintf("%v", rightValue), true
		}
		if opts.useFieldSetEquality && isMultiPicklistFieldMeta(opts.fieldMeta) {
			return schemautil.CanonicalMultiPicklistSetKey(leftValue) != schemautil.CanonicalMultiPicklistSetKey(rightValue), true
		}
		return !picklistAwareEqual(leftValue, rightValue), true
	case "GreaterThan":
		return isGreater(leftValue, rightValue), true
	case "GreaterThanOrEqualTo":
		return isGreaterOrEqual(leftValue, rightValue), true
	case "LessThan":
		return isLess(leftValue, rightValue), true
	case "LessThanOrEqualTo":
		return isLessOrEqual(leftValue, rightValue), true
	case "Contains":
		if isMultiPicklistFieldMeta(opts.fieldMeta) {
			if matched, ferr := includesForField(opts.fieldMeta, leftValue, rightValue); ferr == nil {
				return matched, true
			} else {
				log.Warn().Err(ferr).Str("field", opts.fieldMeta.APIName).Msg("includesForField failed for Contains; falling back to legacy contains")
			}
		}
		return containsString(leftValue, rightValue), true
	case "NotContains":
		if isMultiPicklistFieldMeta(opts.fieldMeta) {
			if matched, ferr := excludesForField(opts.fieldMeta, leftValue, rightValue); ferr == nil {
				return matched, true
			} else {
				log.Warn().Err(ferr).Str("field", opts.fieldMeta.APIName).Msg("excludesForField failed for NotContains; falling back to legacy contains")
			}
		}
		return !containsString(leftValue, rightValue), true
	case "IsNull":
		isNull := leftValue == nil || leftValue == ""
		if bv, ok := rightValue.(bool); ok && !bv {
			return !isNull, true
		}
		return isNull, true
	case "IsNotNull":
		isNull := leftValue == nil || leftValue == ""
		if bv, ok := rightValue.(bool); ok && bv {
			return !isNull, true
		}
		return !isNull, true
	case "IsBlank":
		blank := isBlankValue(leftValue)
		if bv, ok := rightValue.(bool); ok && !bv {
			return !blank, true
		}
		return blank, true
	case "IsNotBlank":
		return !isBlankValue(leftValue), true
	case "StartsWith":
		strFilter := fmt.Sprintf("%v", rightValue)
		if opts.ctx != nil && keyPrefixMatcher != nil {
			if matched, handled := keyPrefixMatcher(opts.ctx, leftValue, strFilter); handled {
				return matched, true
			}
		}
		return strings.HasPrefix(fmt.Sprintf("%v", leftValue), strFilter), true
	case "DoesNotStartWith":
		strFilter := fmt.Sprintf("%v", rightValue)
		if opts.ctx != nil && keyPrefixMatcher != nil {
			if matched, handled := keyPrefixMatcher(opts.ctx, leftValue, strFilter); handled {
				return !matched, true
			}
		}
		return !strings.HasPrefix(fmt.Sprintf("%v", leftValue), strFilter), true
	case "EndsWith":
		strFilter := fmt.Sprintf("%v", rightValue)
		return strings.HasSuffix(fmt.Sprintf("%v", leftValue), strFilter), true
	case "DoesNotEndWith":
		strFilter := fmt.Sprintf("%v", rightValue)
		return !strings.HasSuffix(fmt.Sprintf("%v", leftValue), strFilter), true
	case "Between":
		return isBetween(leftValue, rightValue), true
	case "NotBetween":
		return !isBetween(leftValue, rightValue), true
	case "In":
		return isIn(leftValue, rightValue), true
	case "NotIn":
		return !isIn(leftValue, rightValue), true
	case "Includes":
		if isMultiPicklistFieldMeta(opts.fieldMeta) {
			if matched, ferr := includesForField(opts.fieldMeta, leftValue, rightValue); ferr == nil {
				return matched, true
			} else {
				log.Warn().Err(ferr).Str("field", opts.fieldMeta.APIName).Msg("includesForField failed; falling back to legacy includes")
			}
		}
		return includes(leftValue, rightValue), true
	case "Excludes":
		if isMultiPicklistFieldMeta(opts.fieldMeta) {
			if matched, ferr := excludesForField(opts.fieldMeta, leftValue, rightValue); ferr == nil {
				return matched, true
			} else {
				log.Warn().Err(ferr).Str("field", opts.fieldMeta.APIName).Msg("excludesForField failed; falling back to legacy excludes")
			}
		}
		return excludes(leftValue, rightValue), true
	case "EmptyString":
		return fmt.Sprintf("%v", leftValue) == "", true
	default:
		return false, false
	}
}

// evaluateFlowDecisionRegexOperator is intentionally scoped to Flow decision
// conditions. Query-backed record filters cannot translate RE2 safely, so they
// must fail closed instead of sharing this evaluator.
func evaluateFlowDecisionRegexOperator(operator string, leftValue, rightValue interface{}) (bool, bool) {
	if !isFlowDecisionRegexOperator(operator) {
		return false, false
	}
	matched, valid := matchesFlowRegex(leftValue, rightValue)
	if !valid {
		return false, true
	}
	if normalizeFlowOperator(operator) == "DoesNotMatchRegex" {
		return !matched, true
	}
	return matched, true
}

func isFlowDecisionRegexOperator(operator string) bool {
	switch normalizeFlowOperator(operator) {
	case "MatchesRegex", "DoesNotMatchRegex":
		return true
	default:
		return false
	}
}

// matchesFlowRegex evaluates a metadata-supplied RE2 pattern. The second
// result is false when the pattern is invalid or not a string.
func matchesFlowRegex(leftValue, rightValue interface{}) (bool, bool) {
	pattern, ok := rightValue.(string)
	if !ok {
		return false, false
	}
	compiled, err := regexp.Compile(pattern)
	if err != nil {
		return false, false
	}
	if leftValue == nil {
		return false, true
	}
	return compiled.MatchString(fmt.Sprintf("%v", leftValue)), true
}
