package expression

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestEngine_Evaluate(t *testing.T) {
	e := NewEngine()

	tests := []struct {
		name     string
		expr     string
		env      map[string]interface{}
		expected interface{}
		wantErr  bool
	}{
		{
			name:     "Simple Math",
			expr:     "1 + 1",
			env:      nil,
			expected: 2,
		},
		{
			name:     "Variable Access",
			expr:     "user.Age > 18",
			env:      map[string]interface{}{"user": map[string]interface{}{"Age": 20}},
			expected: true,
		},
		{
			name:     "Nested Access",
			expr:     "record.Account.Name",
			env:      map[string]interface{}{"record": map[string]interface{}{"Account": map[string]interface{}{"Name": "Acme"}}},
			expected: "Acme",
		},
		{
			name:     "Date Function",
			expr:     "TODAY()",
			env:      nil,
			expected: time.Now().Format("2006-01-02"),
		},
		{
			name:     "Date Arithmetic via DATE_ADD",
			expr:     "DATE_ADD(TODAY(), 30)",
			env:      nil,
			expected: time.Now().AddDate(0, 0, 30).Format("2006-01-02"),
		},
		{
			name:     "String Function",
			expr:     "LEN(name)",
			env:      map[string]interface{}{"name": "Nexus"},
			expected: 5,
		},
		// Null-safety for string functions (Salesforce parity: LEN/UPPER/LOWER treat null as empty)
		{
			name:     "LEN nil returns 0",
			expr:     "LEN(val)",
			env:      map[string]interface{}{"val": nil},
			expected: 0,
		},
		{
			name:     "UPPER nil returns empty string",
			expr:     "UPPER(val)",
			env:      map[string]interface{}{"val": nil},
			expected: "",
		},
		{
			name:     "LOWER nil returns empty string",
			expr:     "LOWER(val)",
			env:      map[string]interface{}{"val": nil},
			expected: "",
		},
		{
			name:     "Ternary",
			expr:     "score > 50 ? 'Pass' : 'Fail'",
			env:      map[string]interface{}{"score": 80},
			expected: "Pass",
		},
		{
			name:     "Complex Logic",
			expr:     "(amount * 0.1) > 100",
			env:      map[string]interface{}{"amount": 2000},
			expected: true, // 200 > 100
		},
		// NOT() function
		{
			name:     "NOT true",
			expr:     "NOT(true)",
			env:      nil,
			expected: false,
		},
		{
			name:     "NOT false",
			expr:     "NOT(false)",
			env:      nil,
			expected: true,
		},
		{
			name:     "NOT nested",
			expr:     "NOT(NOT(true))",
			env:      nil,
			expected: true,
		},
		// ISBLANK() — Salesforce parity
		{
			name:     "ISBLANK nil",
			expr:     "ISBLANK(val)",
			env:      map[string]interface{}{"val": nil},
			expected: true,
		},
		{
			name:     "ISBLANK empty string",
			expr:     "ISBLANK(val)",
			env:      map[string]interface{}{"val": ""},
			expected: true,
		},
		{
			name:     "ISBLANK zero — SF returns false",
			expr:     "ISBLANK(val)",
			env:      map[string]interface{}{"val": 0},
			expected: false,
		},
		{
			name:     "ISBLANK non-empty string",
			expr:     "ISBLANK(val)",
			env:      map[string]interface{}{"val": "hello"},
			expected: false,
		},
		{
			name:     "ISBLANK false — SF returns false",
			expr:     "ISBLANK(val)",
			env:      map[string]interface{}{"val": false},
			expected: false,
		},
		// ISNULL() alias
		{
			name:     "ISNULL nil",
			expr:     "ISNULL(val)",
			env:      map[string]interface{}{"val": nil},
			expected: true,
		},
		{
			name:     "ISNULL zero",
			expr:     "ISNULL(val)",
			env:      map[string]interface{}{"val": 0},
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := e.Evaluate(tt.expr, tt.env)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.expected, result)
			}
		})
	}
}

func TestLEN_Unicode(t *testing.T) {
	e := NewEngine()

	tests := []struct {
		name     string
		expr     string
		env      map[string]interface{}
		expected int
	}{
		{
			name:     "ASCII string",
			expr:     "LEN(s)",
			env:      map[string]interface{}{"s": "hello"},
			expected: 5,
		},
		{
			name:     "Chinese characters",
			expr:     "LEN(s)",
			env:      map[string]interface{}{"s": "你好"},
			expected: 2,
		},
		{
			name:     "Mixed ASCII and unicode",
			expr:     "LEN(s)",
			env:      map[string]interface{}{"s": "hi你好"},
			expected: 4,
		},
		{
			name:     "Emoji",
			expr:     "LEN(s)",
			env:      map[string]interface{}{"s": "👍🎉"},
			expected: 2,
		},
		{
			name:     "Empty string",
			expr:     "LEN(s)",
			env:      map[string]interface{}{"s": ""},
			expected: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := e.Evaluate(tt.expr, tt.env)
			assert.NoError(t, err)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestMID_OneBased(t *testing.T) {
	e := NewEngine()

	tests := []struct {
		name     string
		expr     string
		env      map[string]interface{}
		expected string
	}{
		{
			name:     "MID from position 1 (first char)",
			expr:     "MID(s, 1, 3)",
			env:      map[string]interface{}{"s": "hello"},
			expected: "hel",
		},
		{
			name:     "MID from position 2",
			expr:     "MID(s, 2, 3)",
			env:      map[string]interface{}{"s": "hello"},
			expected: "ell",
		},
		{
			name:     "MID from position 1 length 1",
			expr:     "MID(s, 1, 1)",
			env:      map[string]interface{}{"s": "ABCDE"},
			expected: "A",
		},
		{
			name:     "MID with unicode",
			expr:     "MID(s, 2, 2)",
			env:      map[string]interface{}{"s": "你好世界"},
			expected: "好世",
		},
		{
			name:     "MID past end of string",
			expr:     "MID(s, 3, 100)",
			env:      map[string]interface{}{"s": "hello"},
			expected: "llo",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := e.Evaluate(tt.expr, tt.env)
			assert.NoError(t, err)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// TestIF_LazyEvaluation_NilDivisor verifies Fix A: IF must not evaluate the unchosen branch.
// Values come from a map (type=any) so the type-checker passes but the runtime would error
// on the unchosen branch before the fix.
func TestIF_LazyEvaluation_NilDivisor(t *testing.T) {
	e := NewEngine()

	// H1: IF(true, rec.a/12, rec.b/12) — rec.b is nil; unchosen branch must not be evaluated.
	// rec.b is typed as interface{} (from map), so compile succeeds; runtime would divide nil/12.
	env := map[string]interface{}{
		"rec": map[string]interface{}{
			"a": 100,
			"b": nil,
		},
	}
	result, err := e.Evaluate("IF(true, rec.a/12, rec.b/12)", env)
	assert.NoError(t, err, "IF must not evaluate the unchosen branch even when it contains nil arithmetic")
	if f, ok := result.(float64); ok {
		assert.InDelta(t, 8.333333, f, 0.001)
	}

	// Symmetric: false branch chosen, true branch has nil arithmetic — must not error.
	result2, err2 := e.Evaluate("IF(false, rec.b/12, rec.a/12)", env)
	assert.NoError(t, err2)
	if f, ok := result2.(float64); ok {
		assert.InDelta(t, 8.333333, f, 0.001)
	}
}

// TestIF_LazyEvaluation_NilField verifies that IF with a guarded nil map field works correctly.
func TestIF_LazyEvaluation_NilField(t *testing.T) {
	e := NewEngine()

	// rec.x=nil → ISBLANK(rec.x)=true → NOT(true)=false → condition false → return 0.
	// rec.x has type interface{} (from map), so the division rec.x/12 type-checks but
	// would panic at runtime if the branch were evaluated before the fix.
	env := map[string]interface{}{
		"rec": map[string]interface{}{
			"x": nil,
		},
	}
	result, err := e.Evaluate(
		"IF(NOT(ISBLANK(rec.x)) && rec.x != 0, rec.x/12, 0)",
		env,
	)
	assert.NoError(t, err)
	assert.Equal(t, 0, result)
}

// TestPropertyAccess_NullPropagation verifies Fix B: chained property access on nil returns nil.
// Before fix: "cannot fetch DeveloperName from <nil>" error.
func TestPropertyAccess_NullPropagation(t *testing.T) {
	e := NewEngine()

	env := map[string]interface{}{
		"Record": map[string]interface{}{
			"RecordType": nil,
		},
	}

	// H2: Record.RecordType.DeveloperName where RecordType is nil → null-propagates to false.
	result, err := e.Evaluate("Record.RecordType.DeveloperName == 'X'", env)
	assert.NoError(t, err, "chained property access on nil must null-propagate, not error")
	assert.Equal(t, false, result)

	// Absent field (not in map at all) also propagates.
	env2 := map[string]interface{}{
		"Record": map[string]interface{}{},
	}
	result2, err2 := e.Evaluate("Record.RecordType.DeveloperName == 'X'", env2)
	assert.NoError(t, err2)
	assert.Equal(t, false, result2)
}

// TestFormulaRealWorldReplay replays the two production formulas from the #2181 staging log.
func TestFormulaRealWorldReplay(t *testing.T) {
	e := NewEngine()

	// H4: expectedMonthlyAwsRevenue with all-nil inputs must return 0 without error.
	const expectedMonthlyAwsRevenue = `IF( NOT(ISBLANK(__Record.ARR_Amount_USD__c)) && __Record.ARR_Amount_USD__c != 0, (__Record.ARR_Amount_USD__c / 12 * 0.25), IF( NOT(ISBLANK(__Record.Amount)) && __Record.Amount != 0, (__Record.Amount / __Record.Rate__c / 12 * 0.25), 0 ) )`

	nilRecord := map[string]interface{}{
		"__Record": map[string]interface{}{},
	}
	result, err := e.Evaluate(expectedMonthlyAwsRevenue, nilRecord)
	assert.NoError(t, err, "expectedMonthlyAwsRevenue must evaluate without error when all fields are nil")
	assert.Equal(t, 0, result)

	// isnew: __Record.RecordType.DeveloperName where RecordType is nil → 'No'.
	const isnewFormula = `IF(__Record.RecordType.DeveloperName=='New_Subscription','Yes','No')`
	result2, err2 := e.Evaluate(isnewFormula, map[string]interface{}{
		"__Record": map[string]interface{}{
			"RecordType": nil,
		},
	})
	assert.NoError(t, err2, "isnew formula must evaluate without error when RecordType is nil")
	assert.Equal(t, "No", result2)
}

// TestSFFormulaPatcher_Depth1_NullPropagation regression for issue #2181 r2:
// bare depth-1 relationship member accesses (e.g. Account.Name) must return
// null-propagated results when the relation root is an empty map (the
// canonical form augmentRecordWithRelations installs for definitive-null
// relations), not error with "type nil has no field Name".
func TestSFFormulaPatcher_Depth1_NullPropagation(t *testing.T) {
	e := NewEngine()

	// TEXT(Account.Name) with Account={} (empty map — definitive-null relation)
	// must return "" via map-key-lookup returning nil → TEXT(nil) = "".
	result, err := e.Evaluate("TEXT(Account.Name)", map[string]interface{}{"Account": map[string]interface{}{}})
	assert.NoError(t, err, "TEXT(Account.Name) with empty-map Account must not error")
	assert.Equal(t, "", result)

	// IF(RecordType.DeveloperName=='New_Subscription','Yes','No') with RecordType={}
	// must return "No": map key lookup → nil == 'New_Subscription' → false → 'No'.
	result2, err2 := e.Evaluate(
		"IF(RecordType.DeveloperName == 'New_Subscription', 'Yes', 'No')",
		map[string]interface{}{"RecordType": map[string]interface{}{}},
	)
	assert.NoError(t, err2, "IF with empty-map RecordType must not error")
	assert.Equal(t, "No", result2)
}
