package formula

import (
	"encoding/json"
	"fmt"
	"strings"
	"sync"
	"unicode"

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

const FormulaExprV1EvaluatorKind = "formula_expr_v1"

type FunctionBundle struct {
	Functions []FunctionDefinition `json:"functions"`
}

type FunctionDefinition struct {
	Name      string `json:"name"`
	MinArgs   int    `json:"minArgs"`
	MaxArgs   int    `json:"maxArgs"`
	Predicate string `json:"predicate,omitempty"`
	// SQLLowering names a built-in L0 SQL-lowering strategy that the function's
	// SQL emission should use, so the app-blind engine dispatches on this flag
	// instead of hardcoding the function name. Empty selects the generic path.
	SQLLowering string                   `json:"sqlLowering,omitempty"`
	Return      FunctionReturnDefinition `json:"return"`
	Args        []spec.ArgSpec           `json:"args,omitempty"`
	VarArg      *spec.ArgSpec            `json:"varArg,omitempty"`
	Evaluator   FunctionEvaluator        `json:"evaluator"`
}

// SQLLoweringMultipicklistContainsAll is the built-in SQL-lowering for multi-select
// picklist "contains all tokens" functions supplied by an L1 bundle. The generic
// MultiPicklistJSONContains* primitives stay in L0; only dispatch moves to this flag.
const SQLLoweringMultipicklistContainsAll = "multipicklist_contains_all"

type FunctionReturnDefinition struct {
	Kind      string         `json:"kind"`
	Type      spec.ValueType `json:"type,omitempty"`
	MergeArgs []int          `json:"mergeArgs,omitempty"`
}

type FunctionEvaluator struct {
	Kind   string   `json:"kind"`
	Params []string `json:"params,omitempty"`
	Body   string   `json:"body,omitempty"`
}

type compiledFunctionDefinition struct {
	spec       spec.FunctionSpec
	fn         func(params ...interface{}) (interface{}, error)
	definition FunctionDefinition
}

// ExecutableFunctionRegistry is the runtime formula-function bundle snapshot
// used by consumers that need the registered executable metadata as well as the
// call-shape catalog. It keeps app-pack function definitions and their spec
// registry together so downstream compilers do not invent a second read path.
type ExecutableFunctionRegistry struct {
	specRegistry *spec.Registry
	definitions  map[string]FunctionDefinition
	evaluators   map[string]func(params ...interface{}) (interface{}, error)
}

var runtimeFunctionBundles = struct {
	sync.RWMutex
	bundles    []FunctionBundle
	executable *ExecutableFunctionRegistry
}{}

func ParseFunctionBundlePayload(payload []byte) (FunctionBundle, error) {
	var bundle FunctionBundle
	if err := json.Unmarshal(payload, &bundle); err != nil {
		return FunctionBundle{}, fmt.Errorf("parse formula function bundle: %w", err)
	}
	if err := ValidateFunctionBundles([]FunctionBundle{bundle}); err != nil {
		return FunctionBundle{}, err
	}
	return bundle.clone(), nil
}

func ValidateFunctionBundles(bundles []FunctionBundle) error {
	_, err := compileFunctionBundles(spec.DefaultRegistry().Clone(), bundles)
	return err
}

// FunctionRegistryWithBundles returns a formula-function catalog composed from
// the L0 default registry plus the supplied source/runtime bundle declarations.
// It exposes the same registry surface that Engine.RegisterFunctionBundles uses
// without requiring SQL compilers to own evaluator implementations.
func FunctionRegistryWithBundles(bundles []FunctionBundle) (*spec.Registry, error) {
	registry, err := ExecutableFunctionRegistryWithBundles(bundles)
	if err != nil {
		return nil, err
	}
	return registry.SpecRegistry(), nil
}

func ExecutableFunctionRegistryWithBundles(bundles []FunctionBundle) (*ExecutableFunctionRegistry, error) {
	registry := spec.DefaultRegistry().Clone()
	compiled, err := compileFunctionBundles(registry, CloneFunctionBundles(bundles))
	if err != nil {
		return nil, err
	}
	definitions := make(map[string]FunctionDefinition, len(compiled))
	evaluators := make(map[string]func(params ...interface{}) (interface{}, error), len(compiled))
	for _, entry := range compiled {
		definitions[entry.spec.Name] = entry.definition.clone()
		evaluators[entry.spec.Name] = entry.fn
	}
	return &ExecutableFunctionRegistry{
		specRegistry: registry,
		definitions:  definitions,
		evaluators:   evaluators,
	}, nil
}

// ActiveRuntimeFunctionRegistry returns a registry snapshot using the currently
// registered runtime bundle metadata. Startup/setup code keeps that process
// state synchronized from installed or candidate app-pack declarations.
func ActiveRuntimeFunctionRegistry() (*spec.Registry, error) {
	registry, err := ActiveRuntimeExecutableFunctionRegistry()
	if err != nil {
		return nil, err
	}
	return registry.SpecRegistry(), nil
}

func ActiveRuntimeExecutableFunctionRegistry() (*ExecutableFunctionRegistry, error) {
	runtimeFunctionBundles.RLock()
	cached := runtimeFunctionBundles.executable
	if cached != nil {
		cloned := cached.Clone()
		runtimeFunctionBundles.RUnlock()
		return cloned, nil
	}
	bundles := cloneFunctionBundles(runtimeFunctionBundles.bundles)
	runtimeFunctionBundles.RUnlock()
	compiled, err := ExecutableFunctionRegistryWithBundles(bundles)
	if err != nil {
		return nil, err
	}
	runtimeFunctionBundles.Lock()
	if runtimeFunctionBundles.executable == nil {
		runtimeFunctionBundles.executable = compiled
	}
	cached = runtimeFunctionBundles.executable
	runtimeFunctionBundles.Unlock()
	return cached.Clone(), nil
}

func (r *ExecutableFunctionRegistry) SpecRegistry() *spec.Registry {
	if r == nil || r.specRegistry == nil {
		return spec.DefaultRegistry().Clone()
	}
	return r.specRegistry.Clone()
}

func (r *ExecutableFunctionRegistry) LookupFunctionDefinition(name string) (FunctionDefinition, bool) {
	if r == nil || len(r.definitions) == 0 {
		return FunctionDefinition{}, false
	}
	def, ok := r.definitions[name]
	if !ok {
		return FunctionDefinition{}, false
	}
	return def.clone(), true
}

func (r *ExecutableFunctionRegistry) Clone() *ExecutableFunctionRegistry {
	if r == nil {
		return nil
	}
	clone := &ExecutableFunctionRegistry{
		specRegistry: r.SpecRegistry(),
		definitions:  make(map[string]FunctionDefinition, len(r.definitions)),
		evaluators:   make(map[string]func(params ...interface{}) (interface{}, error), len(r.evaluators)),
	}
	for name, def := range r.definitions {
		clone.definitions[name] = def.clone()
	}
	for name, evaluator := range r.evaluators {
		clone.evaluators[name] = evaluator
	}
	return clone
}

func (r *ExecutableFunctionRegistry) hasEvaluators() bool {
	return r != nil && len(r.evaluators) > 0
}

func (e *Engine) registerExecutableFunctionRegistry(registry *ExecutableFunctionRegistry) error {
	if e == nil {
		return fmt.Errorf("formula function bundle registration requires an engine")
	}
	if registry == nil || len(registry.evaluators) == 0 {
		return nil
	}
	nextRegistry := registry.SpecRegistry()
	if e.registeredFunctions == nil {
		e.registeredFunctions = make(map[string]func(params ...interface{}) (interface{}, error))
	}
	for name, evaluator := range registry.evaluators {
		if evaluator == nil {
			return fmt.Errorf("formula function bundle: function %q has nil evaluator", name)
		}
		if e.functionRegistry != nil {
			if _, exists := e.functionRegistry.Lookup(name); exists {
				return fmt.Errorf("formula function bundle: duplicate registration for function %q", name)
			}
		}
		if _, exists := e.registeredFunctions[name]; exists {
			return fmt.Errorf("formula function bundle: duplicate evaluator registration for function %q", name)
		}
	}
	for name, evaluator := range registry.evaluators {
		e.registeredFunctions[name] = evaluator
	}
	e.functionRegistry = nextRegistry
	e.functionPatterns = buildFunctionPatterns(e.functionRegistry)
	e.clearNormalizedFormulaCache()
	e.rebuildExpressionEngine()
	return nil
}

func RegisterRuntimeFunctionBundles(bundles []FunctionBundle) error {
	next := cloneFunctionBundles(bundles)
	executable, err := ExecutableFunctionRegistryWithBundles(next)
	if err != nil {
		return err
	}
	runtimeFunctionBundles.Lock()
	runtimeFunctionBundles.bundles = next
	runtimeFunctionBundles.executable = executable
	runtimeFunctionBundles.Unlock()
	// Update the package-level normalization patterns so ToSQLWithMapper
	// and other callers normalize bundle function names case-insensitively.
	rebuildGlobalFunctionPatterns()
	return nil
}

func activeRuntimeFunctionBundles() []FunctionBundle {
	runtimeFunctionBundles.RLock()
	defer runtimeFunctionBundles.RUnlock()
	return cloneFunctionBundles(runtimeFunctionBundles.bundles)
}

func (e *Engine) RegisterFunctionBundle(bundle FunctionBundle) error {
	return e.RegisterFunctionBundles([]FunctionBundle{bundle})
}

func (e *Engine) RegisterFunctionBundles(bundles []FunctionBundle) error {
	if e == nil {
		return fmt.Errorf("formula function bundle registration requires an engine")
	}
	base := e.functionRegistry
	if base == nil {
		base = spec.DefaultRegistry()
	}
	nextRegistry := base.Clone()
	compiled, err := compileFunctionBundles(nextRegistry, bundles)
	if err != nil {
		return err
	}
	if len(compiled) == 0 {
		return nil
	}
	if e.registeredFunctions == nil {
		e.registeredFunctions = make(map[string]func(params ...interface{}) (interface{}, error))
	}
	for _, entry := range compiled {
		if _, exists := e.registeredFunctions[entry.spec.Name]; exists {
			return fmt.Errorf("formula function bundle: duplicate evaluator registration for function %q", entry.spec.Name)
		}
	}
	for _, entry := range compiled {
		e.registeredFunctions[entry.spec.Name] = entry.fn
	}
	e.functionRegistry = nextRegistry
	e.functionPatterns = buildFunctionPatterns(e.functionRegistry)
	e.clearNormalizedFormulaCache()
	e.rebuildExpressionEngine()
	return nil
}

func compileFunctionBundles(registry *spec.Registry, bundles []FunctionBundle) ([]compiledFunctionDefinition, error) {
	if registry == nil {
		registry = spec.DefaultRegistry().Clone()
	}
	var compiled []compiledFunctionDefinition
	for bundleIndex, bundle := range bundles {
		for functionIndex, fn := range bundle.Functions {
			entry, err := compileFunctionDefinition(registry, fn)
			if err != nil {
				return nil, fmt.Errorf("formula function bundle[%d].functions[%d]: %w", bundleIndex, functionIndex, err)
			}
			if err := registry.Register(entry.spec); err != nil {
				return nil, fmt.Errorf("formula function bundle[%d].functions[%d]: register %q: %w", bundleIndex, functionIndex, entry.spec.Name, err)
			}
			compiled = append(compiled, entry)
		}
	}
	return compiled, nil
}

func compileFunctionDefinition(registry *spec.Registry, fn FunctionDefinition) (compiledFunctionDefinition, error) {
	specDef := fn.Spec()
	if strings.TrimSpace(specDef.Name) == "" {
		return compiledFunctionDefinition{}, fmt.Errorf("function name is required")
	}
	if specDef.MaxArgs != spec.Unbounded && specDef.MinArgs > specDef.MaxArgs {
		return compiledFunctionDefinition{}, fmt.Errorf("%s minArgs %d exceeds maxArgs %d", specDef.Name, specDef.MinArgs, specDef.MaxArgs)
	}
	if err := validateReturnDefinition(fn.Return); err != nil {
		return compiledFunctionDefinition{}, fmt.Errorf("%s return: %w", specDef.Name, err)
	}
	if err := validateFunctionPredicate(fn.Predicate); err != nil {
		return compiledFunctionDefinition{}, fmt.Errorf("%s predicate: %w", specDef.Name, err)
	}
	evaluator, err := compileFunctionEvaluator(registry, specDef, fn.Evaluator)
	if err != nil {
		return compiledFunctionDefinition{}, fmt.Errorf("%s evaluator: %w", specDef.Name, err)
	}
	return compiledFunctionDefinition{spec: specDef, fn: evaluator, definition: fn.clone()}, nil
}

func (fn FunctionDefinition) Spec() spec.FunctionSpec {
	out := spec.FunctionSpec{
		Name:    strings.TrimSpace(fn.Name),
		MinArgs: fn.MinArgs,
		MaxArgs: fn.MaxArgs,
		Return:  fn.Return.toSpec(),
	}
	switch strings.TrimSpace(fn.Predicate) {
	case "", "none":
	case "even_arity":
		out.Predicate = spec.RequireEvenArity(out.Name)
	}
	out.Args = append([]spec.ArgSpec(nil), fn.Args...)
	if fn.VarArg != nil {
		varArg := *fn.VarArg
		out.VarArg = &varArg
	}
	return out
}

func (r FunctionReturnDefinition) toSpec() spec.ReturnSpec {
	switch strings.TrimSpace(r.Kind) {
	case "fixed":
		return spec.ReturnSpec{Kind: spec.ReturnFixed, Fixed: r.Type}
	case "merge_args":
		return spec.ReturnSpec{Kind: spec.ReturnMergeArgs, MergeArgs: append([]int(nil), r.MergeArgs...)}
	case "merge_even_args_plus_last":
		return spec.ReturnSpec{Kind: spec.ReturnMergeEvenArgsPlusLast}
	default:
		return spec.ReturnSpec{}
	}
}

func validateReturnDefinition(r FunctionReturnDefinition) error {
	switch strings.TrimSpace(r.Kind) {
	case "fixed":
		if strings.TrimSpace(string(r.Type)) == "" {
			return fmt.Errorf("fixed return requires type")
		}
	case "merge_args":
		if len(r.MergeArgs) == 0 {
			return fmt.Errorf("merge_args return requires mergeArgs")
		}
	case "merge_even_args_plus_last":
	default:
		return fmt.Errorf("unsupported return kind %q", r.Kind)
	}
	return nil
}

func validateFunctionPredicate(predicate string) error {
	switch strings.TrimSpace(predicate) {
	case "", "none", "even_arity":
		return nil
	default:
		return fmt.Errorf("unsupported predicate %q", predicate)
	}
}

func compileFunctionEvaluator(registry *spec.Registry, specDef spec.FunctionSpec, evaluator FunctionEvaluator) (func(params ...interface{}) (interface{}, error), error) {
	switch strings.TrimSpace(evaluator.Kind) {
	case FormulaExprV1EvaluatorKind:
		return compileFormulaExprV1Evaluator(registry, specDef, evaluator)
	case "":
		return nil, fmt.Errorf("evaluator kind is required")
	default:
		return nil, fmt.Errorf("unsupported evaluator kind %q", evaluator.Kind)
	}
}

func compileFormulaExprV1Evaluator(registry *spec.Registry, specDef spec.FunctionSpec, evaluator FunctionEvaluator) (func(params ...interface{}) (interface{}, error), error) {
	if specDef.MaxArgs == spec.Unbounded {
		return nil, fmt.Errorf("%s requires bounded arity in this slice", FormulaExprV1EvaluatorKind)
	}
	if len(evaluator.Params) != specDef.MinArgs {
		return nil, fmt.Errorf("%s params length %d does not match minArgs %d", FormulaExprV1EvaluatorKind, len(evaluator.Params), specDef.MinArgs)
	}
	body := replaceFormulaOperators(strings.TrimSpace(evaluator.Body))
	if body == "" {
		return nil, fmt.Errorf("%s body is required", FormulaExprV1EvaluatorKind)
	}
	paramNames := make([]string, len(evaluator.Params))
	validationEnv := map[string]interface{}{}
	for i, raw := range evaluator.Params {
		name := strings.TrimSpace(raw)
		if !isFormulaFunctionParamName(name) {
			return nil, fmt.Errorf("%s has invalid param name %q", FormulaExprV1EvaluatorKind, raw)
		}
		if _, exists := validationEnv[name]; exists {
			return nil, fmt.Errorf("%s has duplicate param name %q", FormulaExprV1EvaluatorKind, name)
		}
		paramNames[i] = name
		validationEnv[name] = zeroValueForArgSpec(specDef, i)
	}
	bodyRegistry := spec.DefaultRegistry().Clone()
	if registry != nil {
		bodyRegistry = registry.Clone()
	}
	if _, ok := bodyRegistry.Lookup(specDef.Name); !ok {
		if err := bodyRegistry.Register(specDef); err != nil {
			return nil, fmt.Errorf("%s body registry: %w", FormulaExprV1EvaluatorKind, err)
		}
	}
	exprEngine := expression.NewEngine(expression.WithFunctionRegistry(bodyRegistry))
	if err := exprEngine.Validate(body, validationEnv); err != nil {
		return nil, fmt.Errorf("%s body does not compile: %w", FormulaExprV1EvaluatorKind, err)
	}
	return func(params ...interface{}) (interface{}, error) {
		if err := specDef.Validate(len(params)); err != nil {
			return nil, err
		}
		env := make(map[string]interface{}, len(paramNames))
		for i, name := range paramNames {
			env[name] = params[i]
		}
		return exprEngine.Evaluate(body, env)
	}, nil
}

func zeroValueForArgSpec(specDef spec.FunctionSpec, index int) interface{} {
	argSpec, ok := specDef.ArgSpecAt(index)
	if !ok {
		return ""
	}
	switch argSpec.Type {
	case spec.TypeNumber:
		return 0
	case spec.TypeBoolean:
		return false
	case spec.TypeDate, spec.TypeDatetime:
		return "2026-01-01"
	default:
		return ""
	}
}

func isFormulaFunctionParamName(name string) bool {
	if name == "" {
		return false
	}
	for i, r := range name {
		if i == 0 {
			if r != '_' && !unicode.IsLetter(r) {
				return false
			}
			continue
		}
		if r != '_' && !unicode.IsLetter(r) && !unicode.IsDigit(r) {
			return false
		}
	}
	return true
}

func cloneFunctionBundles(bundles []FunctionBundle) []FunctionBundle {
	out := make([]FunctionBundle, 0, len(bundles))
	for _, bundle := range bundles {
		out = append(out, bundle.clone())
	}
	return out
}

// CloneFunctionBundles returns an independent copy of bundle declarations.
func CloneFunctionBundles(bundles []FunctionBundle) []FunctionBundle {
	return cloneFunctionBundles(bundles)
}

func (b FunctionBundle) clone() FunctionBundle {
	out := FunctionBundle{Functions: make([]FunctionDefinition, 0, len(b.Functions))}
	for _, fn := range b.Functions {
		out.Functions = append(out.Functions, fn.clone())
	}
	return out
}

func (fn FunctionDefinition) clone() FunctionDefinition {
	out := fn
	out.Return.MergeArgs = append([]int(nil), fn.Return.MergeArgs...)
	out.Args = append([]spec.ArgSpec(nil), fn.Args...)
	if fn.VarArg != nil {
		varArg := *fn.VarArg
		out.VarArg = &varArg
	}
	out.Evaluator.Params = append([]string(nil), fn.Evaluator.Params...)
	return out
}
