package formula

import (
	"fmt"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/formula/spec"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestEngineWithFunctionRegistryAcceptsEngineLocalFunction(t *testing.T) {
	registry := spec.DefaultRegistry().Clone()
	if err := registry.Register(spec.FunctionSpec{
		Name:    "TWICE",
		MinArgs: 1,
		MaxArgs: 1,
		Return:  spec.ReturnSpec{Kind: spec.ReturnFixed, Fixed: spec.TypeNumber},
		Args:    []spec.ArgSpec{{Type: spec.TypeNumber}},
	}); err != nil {
		t.Fatalf("register TWICE spec: %v", err)
	}

	engine := NewEngine(WithFunctionRegistry(registry))
	engine.RegisterFunction("TWICE", func(params ...interface{}) (interface{}, error) {
		if len(params) != 1 {
			return nil, fmt.Errorf("TWICE expects 1 arg, got %d", len(params))
		}
		n, ok := params[0].(int)
		if !ok {
			return nil, fmt.Errorf("TWICE argument must be int, got %T", params[0])
		}
		return n * 2, nil
	})

	if err := engine.ValidateSyntax(`TWICE(Amount)`); err != nil {
		t.Fatalf("ValidateSyntax with injected TWICE registry = %v; want nil", err)
	}
	if err := engine.ValidateSyntax(`twice(Amount)`); err != nil {
		t.Fatalf("ValidateSyntax with lower-case injected TWICE registry = %v; want nil", err)
	}
	if err := engine.ValidateSyntax(`TWICE(Amount, 2)`); err == nil || !strings.Contains(err.Error(), "TWICE expects 1 args") {
		t.Fatalf("ValidateSyntax TWICE arity error = %v; want injected registry arity rejection", err)
	}
	err := engine.ValidateSyntax(`TWIC(Amount)`)
	if err == nil || !strings.Contains(err.Error(), "TWICE") {
		t.Fatalf("ValidateSyntax TWIC error = %v; want injected registry candidate suggestion", err)
	}
	got, err := engine.Evaluate(`TWICE(Amount)`, &Context{Record: map[string]interface{}{"Amount": 21}})
	if err != nil {
		t.Fatalf("Evaluate TWICE = %v; want nil", err)
	}
	if got != 42 {
		t.Fatalf("Evaluate TWICE = %v; want 42", got)
	}

	defaultEngine := NewEngine()
	if err := defaultEngine.ValidateSyntax(`TWICE(Amount)`); err == nil || !strings.Contains(err.Error(), "TWICE") {
		t.Fatalf("default ValidateSyntax TWICE error = %v; want unknown function", err)
	}
}

func TestEngineWithFunctionRegistryDrivesBuiltinRuntimeArity(t *testing.T) {
	registry := spec.NewRegistry()
	engine := NewEngine(WithFunctionRegistry(registry))

	got, err := engine.Evaluate(`LEN(Name)`, &Context{Record: map[string]interface{}{"Name": "Acme"}})
	if err == nil {
		t.Fatalf("Evaluate LEN with registry that omits LEN = %v, nil; want engine-local registry rejection", got)
	}
	if !strings.Contains(err.Error(), "LEN") {
		t.Fatalf("Evaluate LEN with registry that omits LEN error = %v; want LEN registry rejection", err)
	}

	defaultEngine := NewEngine()
	got, err = defaultEngine.Evaluate(`LEN(Name)`, &Context{Record: map[string]interface{}{"Name": "Acme"}})
	if err != nil {
		t.Fatalf("default Evaluate LEN = %v; want nil", err)
	}
	if got != 4 {
		t.Fatalf("default Evaluate LEN = %v; want 4", got)
	}
}

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

	if err := engine.checkArity("BCRYPT", 2); err != nil {
		t.Fatalf("checkArity BCRYPT(2) = %v; want active registry acceptance", err)
	}
}

func TestRegisterFunctionRejectsInvalidAndDuplicateEvaluators(t *testing.T) {
	engine := NewEngine()
	evaluator := func(params ...interface{}) (interface{}, error) {
		return "ok", nil
	}

	if err := engine.RegisterFunction("", evaluator); err == nil || !strings.Contains(err.Error(), "name") {
		t.Fatalf("RegisterFunction empty name error = %v; want name rejection", err)
	}
	if err := engine.RegisterFunction("PACK_EVAL", nil); err == nil || !strings.Contains(err.Error(), "nil evaluator") {
		t.Fatalf("RegisterFunction nil evaluator error = %v; want nil evaluator rejection", err)
	}
	if err := engine.RegisterFunction("PACK_EVAL", evaluator); err != nil {
		t.Fatalf("RegisterFunction PACK_EVAL first registration: %v", err)
	}
	if err := engine.RegisterFunction("PACK_EVAL", evaluator); err == nil || !strings.Contains(err.Error(), "duplicate evaluator") {
		t.Fatalf("RegisterFunction duplicate error = %v; want duplicate evaluator rejection", err)
	}
}

func TestRegisterFunctionBundleRejectsDuplicateEvaluatorRegistration(t *testing.T) {
	engine := NewEngine()
	if err := engine.RegisterFunction("PACK_PREFIX", func(params ...interface{}) (interface{}, error) {
		return "existing", nil
	}); err != nil {
		t.Fatalf("RegisterFunction PACK_PREFIX: %v", err)
	}

	err := engine.RegisterFunctionBundle(testFormulaFunctionBundle(t))
	if err == nil || !strings.Contains(err.Error(), "duplicate evaluator") {
		t.Fatalf("RegisterFunctionBundle duplicate evaluator error = %v; want deterministic duplicate rejection", err)
	}
}

func TestEngineOptionRegisterFunctionSurvivesInjectedRegistryRebuild(t *testing.T) {
	registry := spec.DefaultRegistry().Clone()
	if err := registry.Register(spec.FunctionSpec{
		Name:    "TRIPLE",
		MinArgs: 1,
		MaxArgs: 1,
		Return:  spec.ReturnSpec{Kind: spec.ReturnFixed, Fixed: spec.TypeNumber},
		Args:    []spec.ArgSpec{{Type: spec.TypeNumber}},
	}); err != nil {
		t.Fatalf("register TRIPLE spec: %v", err)
	}

	registerTriple := func(e *Engine) {
		e.RegisterFunction("TRIPLE", func(params ...interface{}) (interface{}, error) {
			if len(params) != 1 {
				return nil, fmt.Errorf("TRIPLE expects 1 arg, got %d", len(params))
			}
			n, ok := params[0].(int)
			if !ok {
				return nil, fmt.Errorf("TRIPLE argument must be int, got %T", params[0])
			}
			return n * 3, nil
		})
	}

	engine := NewEngine(registerTriple, WithFunctionRegistry(registry))
	if err := engine.ValidateSyntax(`TRIPLE(Amount)`); err != nil {
		t.Fatalf("ValidateSyntax TRIPLE = %v; want nil", err)
	}
	got, err := engine.Evaluate(`TRIPLE(Amount)`, &Context{Record: map[string]interface{}{"Amount": 7}})
	if err != nil {
		t.Fatalf("Evaluate TRIPLE = %v; want nil", err)
	}
	if got != 21 {
		t.Fatalf("Evaluate TRIPLE = %v; want 21", got)
	}
}

func TestEngineWithFunctionRegistryClonesCallerRegistry(t *testing.T) {
	registry := spec.DefaultRegistry().Clone()
	engine := NewEngine(WithFunctionRegistry(registry))

	if err := registry.Register(spec.FunctionSpec{
		Name:    "AFTER_ENGINE_CONSTRUCTION",
		MinArgs: 0,
		MaxArgs: 0,
		Return:  spec.ReturnSpec{Kind: spec.ReturnFixed, Fixed: spec.TypeString},
	}); err != nil {
		t.Fatalf("register post-construction spec: %v", err)
	}

	err := engine.ValidateSyntax(`AFTER_ENGINE_CONSTRUCTION()`)
	if err == nil || !strings.Contains(err.Error(), "AFTER_ENGINE_CONSTRUCTION") {
		t.Fatalf("ValidateSyntax post-construction function error = %v; want engine-local registry snapshot", err)
	}
}

func TestEngineWithFunctionRegistryDrivesSchemaValidation(t *testing.T) {
	registry := spec.DefaultRegistry().Clone()
	if err := registry.Register(spec.FunctionSpec{
		Name:    "TWICE",
		MinArgs: 1,
		MaxArgs: 1,
		Return:  spec.ReturnSpec{Kind: spec.ReturnFixed, Fixed: spec.TypeNumber},
		Args:    []spec.ArgSpec{{Type: spec.TypeNumber}},
	}); err != nil {
		t.Fatalf("register TWICE spec: %v", err)
	}

	engine := NewEngine(WithFunctionRegistry(registry))
	schema := &models.ObjectMetadata{
		APIName: "Invoice",
		Fields: []models.FieldMetadata{
			{APIName: "Amount", Type: models.FieldType(constants.FieldTypeDouble)},
			{APIName: "Name", Type: models.FieldType(constants.FieldTypeString)},
		},
	}
	numberType := models.FieldType(constants.FieldTypeDouble)

	if err := engine.validateSyntaxWithSchema(`twice(Amount)`, schema, nil, &numberType); err != nil {
		t.Fatalf("validateSyntaxWithSchema lower-case TWICE(number) = %v; want nil", err)
	}
	err := engine.validateSyntaxWithSchema(`TWICE(Name)`, schema, nil, &numberType)
	if err == nil || !strings.Contains(err.Error(), "TWICE argument 1 requires number") {
		t.Fatalf("validateSyntaxWithSchema TWICE(string) error = %v; want injected spec argument-type rejection", err)
	}
}

func TestEngineWithFunctionRegistryDrivesFilterFormulaValidation(t *testing.T) {
	registry := spec.DefaultRegistry().Clone()
	if err := registry.Register(spec.FunctionSpec{
		Name:    "IS_POSITIVE",
		MinArgs: 1,
		MaxArgs: 1,
		Return:  spec.ReturnSpec{Kind: spec.ReturnFixed, Fixed: spec.TypeBoolean},
		Args:    []spec.ArgSpec{{Type: spec.TypeNumber}},
	}); err != nil {
		t.Fatalf("register IS_POSITIVE spec: %v", err)
	}

	engine := NewEngine(WithFunctionRegistry(registry))
	schema := &models.ObjectMetadata{
		APIName: "Invoice",
		Fields: []models.FieldMetadata{
			{APIName: "Amount", Type: models.FieldType(constants.FieldTypeDouble)},
			{APIName: "Name", Type: models.FieldType(constants.FieldTypeString)},
		},
	}

	if err := engine.ValidateFilterFormulaExpression(`IS_POSITIVE(Amount)`, schema); err != nil {
		t.Fatalf("ValidateFilterFormulaExpression IS_POSITIVE(number) = %v; want nil", err)
	}
	err := engine.ValidateFilterFormulaExpression(`IS_POSITIVE(Name)`, schema)
	if err == nil {
		t.Fatal("ValidateFilterFormulaExpression IS_POSITIVE(string) error = nil; want injected spec argument-type rejection")
	}
}
