package expression

import (
	"fmt"
	"math"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/expr-lang/expr"
	"github.com/expr-lang/expr/vm"
)

// Engine is a wrapper around antonmedv/expr
type Engine struct {
	programCache map[string]*vm.Program
	functions    map[string]func(params ...interface{}) (interface{}, error)
	mu           sync.RWMutex
}

// NewEngine creates a new expression engine
func NewEngine() *Engine {
	return &Engine{
		programCache: make(map[string]*vm.Program),
		functions:    make(map[string]func(params ...interface{}) (interface{}, error)),
	}
}

// Evaluate compiles (if needed) and runs an expression against the given environment
func (e *Engine) Evaluate(expression string, env map[string]interface{}) (interface{}, error) {
	program, err := e.getProgram(expression, env)
	if err != nil {
		return nil, err
	}

	output, err := expr.Run(program, env)
	if err != nil {
		return nil, err
	}
	return output, nil
}

// RegisterFunction registers a custom function
func (e *Engine) RegisterFunction(name string, fn func(params ...interface{}) (interface{}, error)) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.functions == nil {
		e.functions = make(map[string]func(params ...interface{}) (interface{}, error))
	}
	e.functions[name] = fn
	// Clear cache as available functions changed
	e.programCache = make(map[string]*vm.Program)
}

func (e *Engine) getProgram(expression string, env map[string]interface{}) (*vm.Program, error) {
	e.mu.RLock()
	if prog, ok := e.programCache[expression]; ok {
		e.mu.RUnlock()
		return prog, nil
	}
	e.mu.RUnlock()

	e.mu.Lock()
	defer e.mu.Unlock()

	// Double check
	if prog, ok := e.programCache[expression]; ok {
		return prog, nil
	}

	// Define standard functions
	options := []expr.Option{
		expr.Env(env),
		expr.Function("TODAY", func(params ...interface{}) (interface{}, error) {
			return time.Now().Format("2006-01-02"), nil
		}),
		expr.Function("NOW", func(params ...interface{}) (interface{}, error) {
			return time.Now().Format("2006-01-02 15:04:05"), nil
		}),
		expr.Function("LEN", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("LEN requires 1 argument")
			}
			if params[0] == nil {
				return 0, nil // Salesforce: LEN(null) = 0
			}
			s, ok := params[0].(string)
			if !ok {
				return nil, fmt.Errorf("LEN argument must be string")
			}
			return len([]rune(s)), nil
		}),
		expr.Function("UPPER", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("UPPER requires 1 argument")
			}
			if params[0] == nil {
				return "", nil // Salesforce: UPPER(null) = ""
			}
			s, ok := params[0].(string)
			if !ok {
				return nil, fmt.Errorf("UPPER argument must be string")
			}
			return strings.ToUpper(s), nil
		}),
		expr.Function("LOWER", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("LOWER requires 1 argument")
			}
			if params[0] == nil {
				return "", nil // Salesforce: LOWER(null) = ""
			}
			s, ok := params[0].(string)
			if !ok {
				return nil, fmt.Errorf("LOWER argument must be string")
			}
			return strings.ToLower(s), nil
		}),
		expr.Function("ROUND", func(params ...interface{}) (interface{}, error) {
			if len(params) != 2 {
				return nil, fmt.Errorf("ROUND requires 2 arguments")
			}
			val, err := toFloat(params[0])
			if err != nil {
				return nil, fmt.Errorf("ROUND arg 1 must be number")
			}
			prec, err := toInt(params[1])
			if err != nil {
				return nil, fmt.Errorf("ROUND arg 2 must be integer")
			}

			// Salesforce-compatible half-up rounding (ROUND(2.5)=3, not banker's rounding)
			mult := 1.0
			for i := 0; i < prec; i++ {
				mult *= 10
			}
			if val >= 0 {
				return math.Floor(val*mult+0.5) / mult, nil
			}
			return -math.Floor(-val*mult+0.5) / mult, nil
		}),
		expr.Function("IF", func(params ...interface{}) (interface{}, error) {
			if len(params) != 3 {
				return nil, fmt.Errorf("IF requires 3 arguments (condition, true_value, false_value)")
			}
			cond, ok := params[0].(bool)
			if !ok {
				return nil, fmt.Errorf("IF condition must be boolean")
			}
			if cond {
				return params[1], nil
			}
			return params[2], nil
		}),
		expr.Function("OR", func(params ...interface{}) (interface{}, error) {
			for _, p := range params {
				if truthy(p) {
					return true, nil
				}
			}
			return false, nil
		}),
		expr.Function("AND", func(params ...interface{}) (interface{}, error) {
			for _, p := range params {
				if !truthy(p) {
					return false, nil
				}
			}
			return true, nil
		}),
		expr.Function("NOT", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("NOT requires 1 argument")
			}
			return !truthy(params[0]), nil
		}),
		expr.Function("TRUTHY", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("TRUTHY requires 1 argument")
			}
			return truthy(params[0]), nil
		}),
		expr.Function("BEGINS", func(params ...interface{}) (interface{}, error) {
			if len(params) != 2 {
				return nil, fmt.Errorf("BEGINS requires 2 arguments")
			}
			s, ok1 := asString(params[0])
			prefix, ok2 := asString(params[1])
			if !ok1 || !ok2 {
				return nil, fmt.Errorf("BEGINS arguments must be strings")
			}
			return strings.HasPrefix(s, prefix), nil
		}),
		expr.Function("FIND", func(params ...interface{}) (interface{}, error) {
			if len(params) < 2 || len(params) > 3 {
				return nil, fmt.Errorf("FIND requires 2 or 3 arguments")
			}
			substr, ok1 := asString(params[0])
			s, ok2 := asString(params[1])
			if !ok1 || !ok2 {
				return nil, fmt.Errorf("FIND arguments 1 and 2 must be strings")
			}
			start := 0
			if len(params) == 3 {
				n, err := toInt(params[2])
				if err != nil {
					return nil, fmt.Errorf("FIND arg 3 must be integer")
				}
				if n > 0 {
					start = n - 1
				}
			}
			runes := []rune(s)
			if start >= len(runes) {
				return 0, nil
			}
			idx := strings.Index(string(runes[start:]), substr)
			if idx < 0 {
				return 0, nil
			}
			// Convert byte offset back to rune offset
			runeIdx := len([]rune(string(runes[start:])[:idx]))
			return runeIdx + start + 1, nil
		}),
		expr.Function("SUBSTITUTE", func(params ...interface{}) (interface{}, error) {
			if len(params) < 3 || len(params) > 4 {
				return nil, fmt.Errorf("SUBSTITUTE requires 3 or 4 arguments")
			}
			text, ok1 := asString(params[0])
			oldText, ok2 := asString(params[1])
			newText, ok3 := asString(params[2])
			if !ok1 || !ok2 || !ok3 {
				return nil, fmt.Errorf("SUBSTITUTE first 3 arguments must be strings")
			}
			if len(params) == 4 {
				instanceNum, err := toInt(params[3])
				if err != nil {
					return nil, fmt.Errorf("SUBSTITUTE arg 4 must be integer")
				}
				if instanceNum <= 0 {
					return text, nil
				}
				count := 0
				return regexp.MustCompile(regexp.QuoteMeta(oldText)).ReplaceAllStringFunc(text, func(match string) string {
					count++
					if count == instanceNum {
						return newText
					}
					return match
				}), nil
			}
			return strings.ReplaceAll(text, oldText, newText), nil
		}),
		expr.Function("ADDMONTHS", func(params ...interface{}) (interface{}, error) {
			if len(params) != 2 {
				return nil, fmt.Errorf("ADDMONTHS requires 2 arguments")
			}
			t, err := parseDate(params[0])
			if err != nil {
				return nil, fmt.Errorf("ADDMONTHS: %w", err)
			}
			months, err := toInt(params[1])
			if err != nil {
				return nil, fmt.Errorf("ADDMONTHS arg 2 must be integer")
			}
			return t.AddDate(0, months, 0).Format("2006-01-02"), nil
		}),
		expr.Function("DATEVALUE", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("DATEVALUE requires 1 argument")
			}
			t, err := parseDate(params[0])
			if err != nil {
				return nil, fmt.Errorf("DATEVALUE: %w", err)
			}
			return t.Format("2006-01-02"), nil
		}),
		expr.Function("HYPERLINK", func(params ...interface{}) (interface{}, error) {
			if len(params) < 2 {
				return nil, fmt.Errorf("HYPERLINK requires at least 2 arguments")
			}
			if label, ok := asString(params[1]); ok {
				return label, nil
			}
			if url, ok := asString(params[0]); ok {
				return url, nil
			}
			return "", nil
		}),
		expr.Function("DATE_ADD", func(params ...interface{}) (interface{}, error) {
			if len(params) != 2 {
				return nil, fmt.Errorf("DATE_ADD requires 2 arguments (date, days)")
			}
			// Parse date string
			dateStr, ok := params[0].(string)
			if !ok {
				return nil, fmt.Errorf("DATE_ADD date must be string")
			}
			days, err := toInt(params[1])
			if err != nil {
				return nil, fmt.Errorf("DATE_ADD days must be integer")
			}
			t, err := time.Parse("2006-01-02", dateStr)
			if err != nil {
				// Try datetime format
				t, err = time.Parse("2006-01-02 15:04:05", dateStr)
				if err != nil {
					return nil, fmt.Errorf("DATE_ADD date format invalid")
				}
			}
			result := t.AddDate(0, 0, days)
			return result.Format("2006-01-02"), nil
		}),
		// --- Salesforce migration: new formula functions ---
		expr.Function("ISBLANK", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("ISBLANK requires 1 argument")
			}
			v := params[0]
			if v == nil {
				return true, nil
			}
			if s, ok := v.(string); ok {
				return s == "", nil
			}
			return false, nil
		}),
		expr.Function("ISNULL", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("ISNULL requires 1 argument")
			}
			return params[0] == nil, nil
		}),
		expr.Function("ISPICKVAL", func(params ...interface{}) (interface{}, error) {
			if len(params) != 2 {
				return nil, fmt.Errorf("ISPICKVAL requires 2 arguments")
			}
			fieldVal := ""
			if params[0] != nil {
				if s, ok := params[0].(string); ok {
					fieldVal = s
				}
			}
			compareVal := ""
			if params[1] != nil {
				if s, ok := params[1].(string); ok {
					compareVal = s
				}
			}
			return fieldVal == compareVal, nil
		}),
		expr.Function("INCLUDES", func(params ...interface{}) (interface{}, error) {
			if len(params) != 2 {
				return nil, fmt.Errorf("INCLUDES requires 2 arguments")
			}
			fieldVal := ""
			if params[0] != nil {
				if s, ok := params[0].(string); ok {
					fieldVal = s
				}
			}
			searchVal := ""
			if params[1] != nil {
				if s, ok := params[1].(string); ok {
					searchVal = s
				}
			}
			if fieldVal == "" {
				return false, nil
			}
			for _, v := range strings.Split(fieldVal, ";") {
				if strings.TrimSpace(v) == searchVal {
					return true, nil
				}
			}
			return false, nil
		}),
		expr.Function("TEXT", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("TEXT requires 1 argument")
			}
			if params[0] == nil {
				return "", nil
			}
			return fmt.Sprintf("%v", params[0]), nil
		}),
		expr.Function("CASE", func(params ...interface{}) (interface{}, error) {
			if len(params) < 4 || len(params)%2 != 0 {
				return nil, fmt.Errorf("CASE requires expression, value/result pairs, and a default")
			}
			expression := params[0]
			for i := 1; i < len(params)-1; i += 2 {
				if fmt.Sprintf("%v", expression) == fmt.Sprintf("%v", params[i]) {
					return params[i+1], nil
				}
			}
			return params[len(params)-1], nil
		}),
		expr.Function("LEFT", func(params ...interface{}) (interface{}, error) {
			if len(params) != 2 {
				return nil, fmt.Errorf("LEFT requires 2 arguments")
			}
			s, ok := params[0].(string)
			if !ok {
				if params[0] == nil {
					return "", nil
				}
				return nil, fmt.Errorf("LEFT arg 1 must be string")
			}
			n, err := toInt(params[1])
			if err != nil {
				return nil, fmt.Errorf("LEFT arg 2 must be integer")
			}
			runes := []rune(s)
			if n > len(runes) {
				n = len(runes)
			}
			if n < 0 {
				n = 0
			}
			return string(runes[:n]), nil
		}),
		expr.Function("RIGHT", func(params ...interface{}) (interface{}, error) {
			if len(params) != 2 {
				return nil, fmt.Errorf("RIGHT requires 2 arguments")
			}
			s, ok := params[0].(string)
			if !ok {
				if params[0] == nil {
					return "", nil
				}
				return nil, fmt.Errorf("RIGHT arg 1 must be string")
			}
			n, err := toInt(params[1])
			if err != nil {
				return nil, fmt.Errorf("RIGHT arg 2 must be integer")
			}
			runes := []rune(s)
			if n > len(runes) {
				n = len(runes)
			}
			if n < 0 {
				n = 0
			}
			return string(runes[len(runes)-n:]), nil
		}),
		expr.Function("MID", func(params ...interface{}) (interface{}, error) {
			if len(params) != 3 {
				return nil, fmt.Errorf("MID requires 3 arguments")
			}
			s, ok := params[0].(string)
			if !ok {
				if params[0] == nil {
					return "", nil
				}
				return nil, fmt.Errorf("MID arg 1 must be string")
			}
			start, err := toInt(params[1])
			if err != nil {
				return nil, fmt.Errorf("MID arg 2 must be integer")
			}
			length, err := toInt(params[2])
			if err != nil {
				return nil, fmt.Errorf("MID arg 3 must be integer")
			}
			runes := []rune(s)
			// Salesforce MID is 1-based: convert to 0-based index
			start = start - 1
			if start < 0 {
				start = 0
			}
			if start > len(runes) {
				return "", nil
			}
			end := start + length
			if end > len(runes) {
				end = len(runes)
			}
			return string(runes[start:end]), nil
		}),
		expr.Function("CONTAINS", func(params ...interface{}) (interface{}, error) {
			if len(params) != 2 {
				return nil, fmt.Errorf("CONTAINS requires 2 arguments")
			}
			s, ok1 := params[0].(string)
			substr, ok2 := params[1].(string)
			if !ok1 || !ok2 {
				if params[0] == nil || params[1] == nil {
					return false, nil
				}
				return nil, fmt.Errorf("CONTAINS arguments must be strings")
			}
			return strings.Contains(s, substr), nil
		}),
		expr.Function("YEAR", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("YEAR requires 1 argument")
			}
			t, err := parseDate(params[0])
			if err != nil {
				return nil, fmt.Errorf("YEAR: %w", err)
			}
			return t.Year(), nil
		}),
		expr.Function("MONTH", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("MONTH requires 1 argument")
			}
			t, err := parseDate(params[0])
			if err != nil {
				return nil, fmt.Errorf("MONTH: %w", err)
			}
			return int(t.Month()), nil
		}),
		expr.Function("DAY", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("DAY requires 1 argument")
			}
			t, err := parseDate(params[0])
			if err != nil {
				return nil, fmt.Errorf("DAY: %w", err)
			}
			return t.Day(), nil
		}),
		expr.Function("CEILING", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("CEILING requires 1 argument")
			}
			val, err := toFloat(params[0])
			if err != nil {
				return nil, fmt.Errorf("CEILING argument must be number")
			}
			return math.Ceil(val), nil
		}),
		expr.Function("ABS", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("ABS requires 1 argument")
			}
			val, err := toFloat(params[0])
			if err != nil {
				return nil, fmt.Errorf("ABS argument must be number")
			}
			return math.Abs(val), nil
		}),
		expr.Function("MOD", func(params ...interface{}) (interface{}, error) {
			if len(params) != 2 {
				return nil, fmt.Errorf("MOD requires 2 arguments")
			}
			a, err := toFloat(params[0])
			if err != nil {
				return nil, fmt.Errorf("MOD arg 1 must be number")
			}
			b, err := toFloat(params[1])
			if err != nil {
				return nil, fmt.Errorf("MOD arg 2 must be number")
			}
			if b == 0 {
				return nil, fmt.Errorf("MOD division by zero")
			}
			return math.Mod(a, b), nil
		}),
		expr.Function("NULLVALUE", func(params ...interface{}) (interface{}, error) {
			if len(params) != 2 {
				return nil, fmt.Errorf("NULLVALUE requires 2 arguments")
			}
			if params[0] == nil {
				return params[1], nil
			}
			if s, ok := params[0].(string); ok && s == "" {
				return params[1], nil
			}
			return params[0], nil
		}),
	}

	// Add custom functions
	for name, fn := range e.functions {
		options = append(options, expr.Function(name, fn))
	}

	// AST patchers: Fix A (lazy IF) + Fix B (null-propagating chains).
	// Two passes: first collects inner-member markers, second applies rewrites.
	marker := newInnerMemberMarker()
	options = append(options,
		expr.Patch(marker),
		expr.Patch(newSFFormulaPatcher(marker)),
	)

	// Compile
	program, err := expr.Compile(expression, options...)
	if err != nil {
		return nil, err
	}

	e.programCache[expression] = program
	return program, nil
}

// Validation helper
func (e *Engine) Validate(expression string, env map[string]interface{}) error {
	_, err := e.getProgram(expression, env)
	return err
}

func toFloat(v interface{}) (float64, error) {
	switch val := v.(type) {
	case float64:
		return val, nil
	case int:
		return float64(val), nil
	case int64:
		return float64(val), nil
	case float32:
		return float64(val), nil
	case string:
		var f float64
		_, err := fmt.Sscanf(val, "%f", &f)
		return f, err
	}
	return 0, fmt.Errorf("cannot convert %T to float", v)
}

func toInt(v interface{}) (int, error) {
	switch val := v.(type) {
	case int:
		return val, nil
	case float64:
		return int(val), nil
	case int64:
		return int(val), nil
	case float32:
		return int(val), nil
	case string:
		var i int
		_, err := fmt.Sscanf(val, "%d", &i)
		return i, err
	}
	return 0, fmt.Errorf("cannot convert %T to int", v)
}

func parseDate(v interface{}) (time.Time, error) {
	if v == nil {
		return time.Time{}, fmt.Errorf("date is nil")
	}
	if t, ok := v.(time.Time); ok {
		return t, nil
	}
	// Handle days-since-epoch from TODAY() arithmetic
	if days, ok := v.(int); ok {
		return epoch.AddDate(0, 0, days), nil
	}
	if days, ok := v.(float64); ok {
		return epoch.AddDate(0, 0, int(days)), nil
	}
	s, ok := v.(string)
	if !ok {
		return time.Time{}, fmt.Errorf("date must be string or time.Time, got %T", v)
	}
	for _, layout := range []string{"2006-01-02", "2006-01-02 15:04:05", "2006-01-02T15:04:05Z", time.RFC3339} {
		if t, err := time.Parse(layout, s); err == nil {
			return t, nil
		}
	}
	return time.Time{}, fmt.Errorf("cannot parse date: %s", s)
}

func asString(v interface{}) (string, bool) {
	if v == nil {
		return "", true
	}
	s, ok := v.(string)
	return s, ok
}

// epoch is the reference point for DaysSinceEpoch / DaysToDateStr.
var epoch = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)

// DaysSinceEpoch converts a time.Time to an integer day count so
// TODAY()+30 works as native arithmetic in the expr engine.
func DaysSinceEpoch(t time.Time) int {
	return int(t.Truncate(24*time.Hour).Sub(epoch).Hours() / 24)
}

// DaysToDateStr converts a DaysSinceEpoch int back to "2006-01-02".
func DaysToDateStr(days int) string {
	return epoch.AddDate(0, 0, days).Format("2006-01-02")
}

func truthy(v interface{}) bool {
	switch val := v.(type) {
	case nil:
		return false
	case bool:
		return val
	case string:
		return val != "" && strings.ToLower(val) != "false" && val != "0"
	case int:
		return val != 0
	case int64:
		return val != 0
	case float64:
		return val != 0
	default:
		return true
	}
}
