package expression

import (
	"reflect"
	"strconv"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/formula/spec"
)

type normalizedMultiPicklistJSON string

func includesEngineForTest(t *testing.T) *Engine {
	t.Helper()
	registry := spec.DefaultRegistry().Clone()
	if err := registry.Register(spec.FunctionSpec{
		Name:    "INCLUDES",
		MinArgs: 2,
		MaxArgs: 2,
		Return:  spec.ReturnSpec{Kind: spec.ReturnFixed, Fixed: spec.TypeBoolean},
	}); err != nil {
		t.Fatalf("register INCLUDES spec: %v", err)
	}
	return NewEngine(WithFunctionRegistry(registry))
}

func TestBuiltinRuntimeArityUsesOnlyActiveRegistry(t *testing.T) {
	registry := spec.NewRegistry()
	if err := registry.Register(spec.FunctionSpec{
		Name:    "LEN",
		MinArgs: 1,
		MaxArgs: 2,
		Return:  spec.ReturnSpec{Kind: spec.ReturnFixed, Fixed: spec.TypeNumber},
	}); err != nil {
		t.Fatalf("register relaxed LEN spec: %v", err)
	}
	engine := NewEngine(WithFunctionRegistry(registry))

	defer func() {
		if r := recover(); r != nil {
			t.Fatalf("Evaluate LEN(Name, ...) panicked with relaxed injected registry: %v", r)
		}
	}()
	got, err := engine.Evaluate(`LEN(Name, "ignored")`, map[string]interface{}{"Name": "Acme"})
	if err != nil {
		t.Fatalf("Evaluate LEN(Name, ...) with relaxed injected registry = %v; want active registry acceptance", err)
	}
	if got != 4 {
		t.Fatalf("Evaluate LEN(Name, ...) = %v; want 4", got)
	}
}

func TestCalendarPrimitiveRuntimeArityUsesOnlyActiveRegistry(t *testing.T) {
	for _, tc := range []struct {
		name       string
		expression string
		function   string
		returnType spec.ValueType
	}{
		{
			name:       "Salesforce month add",
			expression: `__DATE_ADD_MONTHS__("2024-06-15", 1, "ignored")`,
			function:   "__DATE_ADD_MONTHS__",
			returnType: spec.TypeDate,
		},
		{
			name:       "target-clamp month add",
			expression: `__DATE_ADD_MONTHS_TARGET_CLAMP__("2024-06-15", 1, "ignored")`,
			function:   "__DATE_ADD_MONTHS_TARGET_CLAMP__",
			returnType: spec.TypeDate,
		},
		{
			name:       "calendar day difference",
			expression: `__DATE_DIFF_DAYS__("2024-06-16", "2024-06-15", "ignored")`,
			function:   "__DATE_DIFF_DAYS__",
			returnType: spec.TypeNumber,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			registry := spec.NewRegistry()
			if err := registry.Register(spec.FunctionSpec{
				Name: tc.function, MinArgs: 2, MaxArgs: 3,
				Return: spec.ReturnSpec{Kind: spec.ReturnFixed, Fixed: tc.returnType},
			}); err != nil {
				t.Fatalf("register relaxed %s spec: %v", tc.function, err)
			}
			engine := NewEngine(WithFunctionRegistry(registry))
			if _, err := engine.Evaluate(tc.expression, nil); err != nil {
				t.Fatalf("Evaluate with relaxed active registry: %v", err)
			}

			missing := NewEngine(WithFunctionRegistry(spec.NewRegistry()))
			if _, err := missing.Evaluate(tc.expression, nil); err == nil {
				t.Fatalf("Evaluate with function absent from active registry succeeded; want rejection")
			}
		})
	}
}

func TestIFLoweringRequiresActiveRegistryFunction(t *testing.T) {
	registry := spec.NewRegistry()
	if err := registry.Register(spec.FunctionSpec{
		Name:    "ROUND",
		MinArgs: 2,
		MaxArgs: 2,
		Return:  spec.ReturnSpec{Kind: spec.ReturnFixed, Fixed: spec.TypeNumber},
	}); err != nil {
		t.Fatalf("register ROUND spec: %v", err)
	}
	engine := NewEngine(WithFunctionRegistry(registry))

	got, err := engine.Evaluate(`IF(true, "ok", ROUND("bad", 0))`, nil)
	if err == nil {
		t.Fatalf("Evaluate IF without active IF registry = %v, nil; want eager unlowered branch error", got)
	}
	if !strings.Contains(err.Error(), "ROUND") {
		t.Fatalf("Evaluate IF without active IF registry error = %v; want unlowered ROUND branch error", err)
	}
}

func TestIFLoweringUsesActiveRegistryFunction(t *testing.T) {
	registry := spec.NewRegistry()
	if err := registry.Register(spec.FunctionSpec{
		Name:    "IF",
		MinArgs: 3,
		MaxArgs: 3,
		Return:  spec.ReturnSpec{Kind: spec.ReturnMergeArgs, MergeArgs: []int{1, 2}},
	}); err != nil {
		t.Fatalf("register IF spec: %v", err)
	}
	engine := NewEngine(WithFunctionRegistry(registry))

	got, err := engine.Evaluate(`IF(true, "ok", ROUND("bad", 0))`, nil)
	if err != nil {
		t.Fatalf("Evaluate IF with active IF registry = %v; want lazy lowered success", err)
	}
	if got != "ok" {
		t.Fatalf("Evaluate IF with active IF registry = %v; want ok", got)
	}
}

func TestAndOrLoweringShortCircuitsDeadBranches(t *testing.T) {
	engine := NewEngine()

	got, err := engine.Evaluate(`AND(false, ROUND("bad", 0))`, nil)
	if err != nil {
		t.Fatalf("Evaluate lazy AND returned error: %v", err)
	}
	if got != false {
		t.Fatalf("Evaluate lazy AND = %v, want false", got)
	}

	got, err = engine.Evaluate(`OR(true, ROUND("bad", 0))`, nil)
	if err != nil {
		t.Fatalf("Evaluate lazy OR returned error: %v", err)
	}
	if got != true {
		t.Fatalf("Evaluate lazy OR = %v, want true", got)
	}
}

func TestDateAddMonthsSalesforceDiscriminatorParity(t *testing.T) {
	engine := NewEngine()
	const wantSQL = "DATE(CASE WHEN DAY(?) = DAY(LAST_DAY(?)) THEN LAST_DAY(DATE_ADD(?, INTERVAL ? MONTH)) ELSE DATE_ADD(?, INTERVAL ? MONTH) END)"

	tests := []struct {
		name   string
		date   string
		months int
		want   string
	}{
		{
			name:   "last day of February anchors to last day of March",
			date:   "2019-02-28",
			months: 1,
			want:   "2019-03-31",
		},
		{
			name:   "end of December clamps to leap-year February target",
			date:   "2024-12-31",
			months: 2,
			want:   "2025-02-28",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			expr := `__DATE_ADD_MONTHS__("` + tt.date + `", ` + strconv.Itoa(tt.months) + `)`
			got, err := engine.Evaluate(expr, map[string]interface{}{})
			if err != nil {
				t.Fatalf("Evaluate(%q) error = %v; want Salesforce-compatible date", expr, err)
			}
			if got != tt.want {
				t.Fatalf("Evaluate(%q) = %v, want %s", expr, got, tt.want)
			}

			sql, args, err := ToSQL(expr)
			if err != nil {
				t.Fatalf("ToSQL(%q) error = %v; want SQL lowering", expr, err)
			}
			if sql != wantSQL {
				t.Fatalf("ToSQL(%q) SQL = %q, want Salesforce last-day anchoring CASE %q", expr, sql, wantSQL)
			}
			wantArgs := []interface{}{tt.date, tt.date, tt.date, tt.months, tt.date, tt.months}
			if !reflect.DeepEqual(args, wantArgs) {
				t.Fatalf("ToSQL(%q) args = %#v, want %#v", expr, args, wantArgs)
			}
		})
	}
}

func TestCalendarClampAndDayDifferencePrimitives(t *testing.T) {
	engine := NewEngine()

	for _, tt := range []struct {
		name   string
		date   string
		months int
		want   string
	}{
		{name: "ordinary month end is not anchored", date: "2019-02-28", months: 1, want: "2019-03-28"},
		{name: "day beyond target month clamps", date: "2023-01-31", months: 1, want: "2023-02-28"},
		{name: "negative leap-month clamp", date: "2024-03-31", months: -1, want: "2024-02-29"},
		{name: "cross-year clamp", date: "2023-01-31", months: 13, want: "2024-02-29"},
	} {
		t.Run(tt.name, func(t *testing.T) {
			expression := `__DATE_ADD_MONTHS_TARGET_CLAMP__("` + tt.date + `", ` + strconv.Itoa(tt.months) + `)`
			got, err := engine.Evaluate(expression, nil)
			if err != nil {
				t.Fatalf("Evaluate(%q): %v", expression, err)
			}
			if got != tt.want {
				t.Fatalf("Evaluate(%q) = %v, want %s", expression, got, tt.want)
			}
		})
	}

	for _, tt := range []struct {
		name, end, start string
		want             int64
	}{
		{name: "leap day included", end: "2024-03-01", start: "2024-02-28", want: 2},
		{name: "reverse order", end: "2024-02-28", start: "2024-03-01", want: -2},
		{name: "wide date range does not overflow duration", end: "2500-01-01", start: "1500-01-01", want: 365243},
	} {
		t.Run(tt.name, func(t *testing.T) {
			expression := `__DATE_DIFF_DAYS__("` + tt.end + `", "` + tt.start + `")`
			got, err := engine.Evaluate(expression, nil)
			if err != nil {
				t.Fatalf("Evaluate(%q): %v", expression, err)
			}
			if got != tt.want {
				t.Fatalf("Evaluate(%q) = %#v, want %d", expression, got, tt.want)
			}
		})
	}
	datetimeResult, err := engine.Evaluate(`__DATE_ADD_MONTHS_TARGET_CLAMP__("2024-06-15T12:34:56Z", 1)`, nil)
	if err != nil {
		t.Fatalf("Evaluate DateTime input: %v", err)
	}
	if datetimeResult != "2024-07-15" {
		t.Fatalf("Evaluate DateTime input = %#v, want date-only result", datetimeResult)
	}
	dateAddResult, err := engine.Evaluate(`DATE_ADD("2024-06-15T12:34:56Z", 1)`, nil)
	if err != nil {
		t.Fatalf("Evaluate DATE_ADD DateTime input: %v", err)
	}
	if dateAddResult != "2024-06-16" {
		t.Fatalf("Evaluate DATE_ADD DateTime input = %#v, want date-only result", dateAddResult)
	}

	monthSQL, monthArgs, err := ToSQL(`__DATE_ADD_MONTHS_TARGET_CLAMP__(CreatedDate, 3)`)
	if err != nil {
		t.Fatalf("ToSQL month clamp: %v", err)
	}
	if monthSQL != "DATE(DATE_ADD(CreatedDate, INTERVAL ? MONTH))" || !reflect.DeepEqual(monthArgs, []interface{}{3}) {
		t.Fatalf("month clamp SQL = %q %#v", monthSQL, monthArgs)
	}
	diffSQL, diffArgs, err := ToSQL(`__DATE_DIFF_DAYS__(CloseDate, CreatedDate)`)
	if err != nil {
		t.Fatalf("ToSQL date diff: %v", err)
	}
	if diffSQL != "DATEDIFF(CloseDate, CreatedDate)" || len(diffArgs) != 0 {
		t.Fatalf("date diff SQL = %q %#v", diffSQL, diffArgs)
	}
}

func TestCalendarPrimitiveSQLDateRangeParity(t *testing.T) {
	engine := NewEngine()
	for _, expression := range []string{
		`__DATE_ADD_MONTHS__("9999-12-31", 1)`,
		`__DATE_ADD_MONTHS__("0001-01-01", -1)`,
		`__DATE_ADD_MONTHS_TARGET_CLAMP__("9999-12-31", 1)`,
		`__DATE_ADD_MONTHS_TARGET_CLAMP__("0001-01-01", -1)`,
		`DATE_ADD("9999-12-31", 1)`,
		`DATE_ADD("0001-01-01", -1)`,
	} {
		t.Run(expression, func(t *testing.T) {
			got, err := engine.Evaluate(expression, nil)
			if err != nil {
				t.Fatalf("Evaluate(%q): %v", expression, err)
			}
			if got != nil {
				t.Fatalf("Evaluate(%q) = %#v, want NULL outside SQL DATE range", expression, got)
			}
		})
	}

	for expression, want := range map[string]string{
		`__DATE_ADD_MONTHS_TARGET_CLAMP__("0001-01-01", 0)`:  "0001-01-01",
		`__DATE_ADD_MONTHS_TARGET_CLAMP__("1000-01-01", -1)`: "0999-12-01",
		`__DATE_ADD_MONTHS_TARGET_CLAMP__("9999-12-31", 0)`:  "9999-12-31",
		`DATE_ADD("9999-12-30", 1)`:                          "9999-12-31",
	} {
		got, err := engine.Evaluate(expression, nil)
		if err != nil {
			t.Fatalf("Evaluate(%q): %v", expression, err)
		}
		if got != want {
			t.Fatalf("Evaluate(%q) = %#v, want %q", expression, got, want)
		}
	}

	got, err := engine.Evaluate(`__DATE_DIFF_DAYS__("0000-01-02", "0000-01-01")`, nil)
	if err != nil {
		t.Fatalf("Evaluate year-0000 day difference: %v", err)
	}
	if got != int64(1) {
		t.Fatalf("year-0000 day difference = %#v, want 1", got)
	}
}

func TestIncludesMatchesSemicolonSeparatedWireString(t *testing.T) {
	engine := includesEngineForTest(t)

	got, err := engine.Evaluate(`INCLUDES(Field, "Others")`, map[string]interface{}{
		"Field": `Others`,
	})
	if err != nil {
		t.Fatalf("Evaluate returned error: %v", err)
	}
	if got != true {
		t.Fatalf("Evaluate returned %v, want true", got)
	}
}

func TestIncludesSemicolonRHSLiteralUsesANDSemantics(t *testing.T) {
	engine := includesEngineForTest(t)

	tests := []struct {
		name string
		env  map[string]interface{}
		want bool
	}{
		{
			name: "semicolon stored value contains both tokens",
			env:  map[string]interface{}{"Field": "B;A"},
			want: true,
		},
		{
			name: "slice value contains both tokens",
			env:  map[string]interface{}{"Field": []string{"A", "B"}},
			want: true,
		},
		{
			name: "schema-aware JSON storage contains both tokens",
			env: map[string]interface{}{
				"Field": `["A","B"]`,
				MultiPicklistFieldEnvKey: map[string]interface{}{
					"Field": []string{"A", "B", "C"},
				},
			},
			want: true,
		},
		{
			name: "missing one token is false",
			env:  map[string]interface{}{"Field": "A"},
			want: false,
		},
		{
			name: "schema-aware JSON storage missing one token is false",
			env: map[string]interface{}{
				"Field": `["A"]`,
				MultiPicklistFieldEnvKey: map[string]interface{}{
					"Field": []string{"A", "B", "C"},
				},
			},
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := engine.Evaluate(`INCLUDES(Field, "A;B")`, tt.env)
			if err != nil {
				t.Fatalf("Evaluate returned error: %v", err)
			}
			if got != tt.want {
				t.Fatalf("Evaluate returned %v, want %v", got, tt.want)
			}
		})
	}
}

func TestIncludesNonLiteralSemicolonRHSKeepsSingleValueSemantics(t *testing.T) {
	engine := includesEngineForTest(t)

	got, err := engine.Evaluate(`INCLUDES(Field, WantedTag)`, map[string]interface{}{
		"Field":     []string{"A", "B"},
		"WantedTag": "A;B",
	})
	if err != nil {
		t.Fatalf("Evaluate returned error: %v", err)
	}
	if got != false {
		t.Fatalf(`INCLUDES(Field, WantedTag) with WantedTag="A;B" returned %v, want false single-value RHS semantics`, got)
	}
}

func TestIncludesTreatsJSONStringLookingLiteralAsLiteralString(t *testing.T) {
	engine := includesEngineForTest(t)

	got, err := engine.Evaluate(`INCLUDES(Field, "Others")`, map[string]interface{}{
		"Field": `["Others"]`,
	})
	if err != nil {
		t.Fatalf("Evaluate returned error: %v", err)
	}
	if got != false {
		t.Fatalf("Evaluate returned %v, want false for single JSON-looking literal option", got)
	}

	got, err = engine.Evaluate(`INCLUDES(Field, "[\"Others\"]")`, map[string]interface{}{
		"Field": `["Others"]`,
	})
	if err != nil {
		t.Fatalf("Evaluate returned error: %v", err)
	}
	if got != true {
		t.Fatalf("Evaluate returned %v, want true for exact single literal option match", got)
	}
}

func TestIncludesSchemaAwareStorageEnvelope(t *testing.T) {
	engine := includesEngineForTest(t)

	got, err := engine.Evaluate(`INCLUDES(Field, "Others")`, map[string]interface{}{
		"Field": `["Others"]`,
		MultiPicklistFieldEnvKey: map[string]interface{}{
			"Field": []string{"Others"},
		},
	})
	if err != nil {
		t.Fatalf("Evaluate returned error: %v", err)
	}
	if got != true {
		t.Fatalf(`INCLUDES(Field, "Others") returned %v, want true for storage envelope ["Others"]`, got)
	}
}

func TestIncludesSchemaAwareStorageEnvelopeWithStaleSelection(t *testing.T) {
	engine := includesEngineForTest(t)
	env := map[string]interface{}{
		"Field": `["A","Stale"]`,
		MultiPicklistFieldEnvKey: map[string]interface{}{
			"Field": []string{"A"},
		},
	}

	got, err := engine.Evaluate(`INCLUDES(Field, "A")`, env)
	if err != nil {
		t.Fatalf("Evaluate returned error: %v", err)
	}
	if got != true {
		t.Fatalf(`INCLUDES(Field, "A") returned %v, want true for storage envelope with stale selection`, got)
	}

	got, err = engine.Evaluate(`INCLUDES(Field, "Stale")`, env)
	if err != nil {
		t.Fatalf("Evaluate stale returned error: %v", err)
	}
	if got != true {
		t.Fatalf(`INCLUDES(Field, "Stale") returned %v, want true for undeclared stored selection`, got)
	}
}

func TestIncludesSchemaAwareDefinedStringStorageEnvelope(t *testing.T) {
	engine := includesEngineForTest(t)

	got, err := engine.Evaluate(`INCLUDES(Field, "Others")`, map[string]interface{}{
		"Field": normalizedMultiPicklistJSON(`["Others"]`),
		MultiPicklistFieldEnvKey: map[string]interface{}{
			"Field": []string{"Others"},
		},
	})
	if err != nil {
		t.Fatalf("Evaluate returned error: %v", err)
	}
	if got != true {
		t.Fatalf(`INCLUDES(Field, "Others") returned %v, want true for defined-string storage envelope`, got)
	}
}

func TestIncludesSchemaAwareDeclaredJSONLookingOption(t *testing.T) {
	engine := includesEngineForTest(t)
	env := map[string]interface{}{
		"Field": `["[\"Others\"]"]`,
		MultiPicklistFieldEnvKey: map[string]interface{}{
			"Field": []string{`["Others"]`, "Others"},
		},
	}

	got, err := engine.Evaluate(`INCLUDES(Field, "[\"Others\"]")`, env)
	if err != nil {
		t.Fatalf("Evaluate returned error: %v", err)
	}
	if got != true {
		t.Fatalf(`INCLUDES(Field, "[\"Others\"]") returned %v, want true for declared JSON-looking option`, got)
	}

	got, err = engine.Evaluate(`INCLUDES(Field, "Others")`, env)
	if err != nil {
		t.Fatalf("Evaluate returned error: %v", err)
	}
	if got != false {
		t.Fatalf(`INCLUDES(Field, "Others") returned %v, want false for distinct declared JSON-looking option`, got)
	}
}
