package spec

import (
	"fmt"
	"testing"
)

func TestFunctionSpecValidateBounds(t *testing.T) {
	cases := []struct {
		name    string
		s       FunctionSpec
		argc    int
		wantErr bool
	}{
		{"exact ok", FunctionSpec{Name: "ABS", MinArgs: 1, MaxArgs: 1}, 1, false},
		{"too many", FunctionSpec{Name: "ABS", MinArgs: 1, MaxArgs: 1}, 2, true},
		{"too few", FunctionSpec{Name: "IF", MinArgs: 3, MaxArgs: 3}, 2, true},
		{"range low ok", FunctionSpec{Name: "FIND", MinArgs: 2, MaxArgs: 3}, 2, false},
		{"range high ok", FunctionSpec{Name: "FIND", MinArgs: 2, MaxArgs: 3}, 3, false},
		{"range over", FunctionSpec{Name: "FIND", MinArgs: 2, MaxArgs: 3}, 4, true},
		{"unbounded ok", FunctionSpec{Name: "OR", MinArgs: 1, MaxArgs: Unbounded}, 9, false},
		{"unbounded too few", FunctionSpec{Name: "OR", MinArgs: 1, MaxArgs: Unbounded}, 0, true},
		{"zero arity ok", FunctionSpec{Name: "TODAY", MinArgs: 0, MaxArgs: 0}, 0, false},
		{"zero arity over", FunctionSpec{Name: "TODAY", MinArgs: 0, MaxArgs: 0}, 1, true},
	}
	for _, tc := range cases {
		err := tc.s.Validate(tc.argc)
		if (err != nil) != tc.wantErr {
			t.Errorf("%s: Validate(%d) err=%v, wantErr=%v", tc.name, tc.argc, err, tc.wantErr)
		}
	}
}

func TestFunctionSpecValidatePredicate(t *testing.T) {
	// CASE-style: bounds (>=4, unbounded) plus an even-arity predicate.
	even := FunctionSpec{
		Name:    "CASE",
		MinArgs: 4,
		MaxArgs: Unbounded,
		Predicate: func(argc int) error {
			if argc%2 != 0 {
				return fmt.Errorf("CASE requires an even number of arguments")
			}
			return nil
		},
	}
	if err := even.Validate(4); err != nil {
		t.Errorf("Validate(4) unexpected err: %v", err)
	}
	if err := even.Validate(6); err != nil {
		t.Errorf("Validate(6) unexpected err: %v", err)
	}
	// Below the minimum is rejected by bounds before the predicate runs.
	if err := even.Validate(3); err == nil {
		t.Error("Validate(3) want bounds error, got nil")
	}
	// Above the minimum but odd is rejected by the predicate.
	if err := even.Validate(5); err == nil {
		t.Error("Validate(5) want predicate error, got nil")
	}
}

func TestFunctionSpecBoundsDescription(t *testing.T) {
	cases := []struct {
		s    FunctionSpec
		want string
	}{
		{FunctionSpec{MinArgs: 1, MaxArgs: 1}, "1 args"},
		{FunctionSpec{MinArgs: 2, MaxArgs: 3}, "2-3 args"},
		{FunctionSpec{MinArgs: 1, MaxArgs: Unbounded}, ">=1 args"},
	}
	for _, tc := range cases {
		if got := tc.s.BoundsDescription(); got != tc.want {
			t.Errorf("BoundsDescription() = %q, want %q", got, tc.want)
		}
	}
}

func TestRegistryRegisterGuards(t *testing.T) {
	r := NewRegistry()
	if err := r.Register(FunctionSpec{Name: ""}); err == nil {
		t.Error("Register empty name: want error, got nil")
	}
	if err := r.Register(FunctionSpec{Name: "ABS", MinArgs: 1, MaxArgs: 1}); err != nil {
		t.Fatalf("first Register: %v", err)
	}
	if err := r.Register(FunctionSpec{Name: "ABS", MinArgs: 1, MaxArgs: 1}); err == nil {
		t.Error("duplicate Register: want error, got nil")
	}
	if r.Len() != 1 {
		t.Errorf("Len() = %d, want 1", r.Len())
	}
}

func TestRegistryLookupAndNames(t *testing.T) {
	r := NewRegistry()
	mustRegister(t, r, FunctionSpec{Name: "ABS", MinArgs: 1, MaxArgs: 1})
	mustRegister(t, r, FunctionSpec{Name: "OR", MinArgs: 1, MaxArgs: Unbounded})

	if _, ok := r.Lookup("ABS"); !ok {
		t.Error("Lookup ABS: want found")
	}
	if _, ok := r.Lookup("NOPE"); ok {
		t.Error("Lookup NOPE: want not found")
	}
	if got := len(r.Names()); got != 2 {
		t.Errorf("Names() len = %d, want 2", got)
	}
}

func TestRegistryCheckArityUsesReceiverCatalog(t *testing.T) {
	r := NewRegistry()
	mustRegister(t, r, FunctionSpec{Name: "CUSTOM", MinArgs: 2, MaxArgs: 2})

	if err := r.CheckArity("CUSTOM", 2); err != nil {
		t.Fatalf("CheckArity CUSTOM(2) = %v; want nil", err)
	}
	if err := r.CheckArity("CUSTOM", 1); err == nil {
		t.Fatal("CheckArity CUSTOM(1) = nil; want arity error")
	}
	if err := r.CheckArity("LEN", 1); err == nil {
		t.Fatal("CheckArity LEN on custom registry = nil; want unregistered error")
	}
}

func TestRegistryCloneDeepCopiesFunctionSpecInternals(t *testing.T) {
	r := NewRegistry()
	mustRegister(t, r, FunctionSpec{
		Name:    "CUSTOM",
		MinArgs: 1,
		MaxArgs: Unbounded,
		Args: []ArgSpec{
			{Type: TypeNumber},
		},
		VarArg: &ArgSpec{Type: TypeString},
		Return: ReturnSpec{
			Kind:      ReturnMergeArgs,
			MergeArgs: []int{0},
		},
	})

	clone := r.Clone()

	source, ok := r.Lookup("CUSTOM")
	if !ok {
		t.Fatal("Lookup CUSTOM on source: want found")
	}
	source.Args[0].Type = TypeBoolean
	source.VarArg.Type = TypeBoolean
	source.Return.MergeArgs[0] = 42

	got, ok := clone.Lookup("CUSTOM")
	if !ok {
		t.Fatal("Lookup CUSTOM on clone: want found")
	}
	if got.Args[0].Type != TypeNumber {
		t.Fatalf("cloned Args[0].Type = %s; want snapshot %s", got.Args[0].Type, TypeNumber)
	}
	if got.VarArg == nil || got.VarArg.Type != TypeString {
		t.Fatalf("cloned VarArg = %+v; want snapshot TypeString", got.VarArg)
	}
	if got.Return.MergeArgs[0] != 0 {
		t.Fatalf("cloned Return.MergeArgs[0] = %d; want snapshot 0", got.Return.MergeArgs[0])
	}
}

func TestRegistryRegisterDeepCopiesFunctionSpecInternals(t *testing.T) {
	r := NewRegistry()
	original := FunctionSpec{
		Name:    "CUSTOM",
		MinArgs: 1,
		MaxArgs: Unbounded,
		Args: []ArgSpec{
			{Type: TypeNumber},
		},
		VarArg: &ArgSpec{Type: TypeString},
		Return: ReturnSpec{
			Kind:      ReturnMergeArgs,
			MergeArgs: []int{0},
		},
	}
	mustRegister(t, r, original)

	original.Args[0].Type = TypeBoolean
	original.VarArg.Type = TypeBoolean
	original.Return.MergeArgs[0] = 42

	got, ok := r.Lookup("CUSTOM")
	if !ok {
		t.Fatal("Lookup CUSTOM: want found")
	}
	if got.Args[0].Type != TypeNumber {
		t.Fatalf("registered Args[0].Type = %s; want detached %s", got.Args[0].Type, TypeNumber)
	}
	if got.VarArg == nil || got.VarArg.Type != TypeString {
		t.Fatalf("registered VarArg = %+v; want detached TypeString", got.VarArg)
	}
	if got.Return.MergeArgs[0] != 0 {
		t.Fatalf("registered Return.MergeArgs[0] = %d; want detached 0", got.Return.MergeArgs[0])
	}
}

func TestRegistryLookupDeepCopiesFunctionSpecInternals(t *testing.T) {
	r := NewRegistry()
	mustRegister(t, r, FunctionSpec{
		Name:    "CUSTOM",
		MinArgs: 1,
		MaxArgs: Unbounded,
		Args: []ArgSpec{
			{Type: TypeNumber},
		},
		VarArg: &ArgSpec{Type: TypeString},
		Return: ReturnSpec{
			Kind:      ReturnMergeArgs,
			MergeArgs: []int{0},
		},
	})

	first, ok := r.Lookup("CUSTOM")
	if !ok {
		t.Fatal("first Lookup CUSTOM: want found")
	}
	first.Args[0].Type = TypeBoolean
	first.VarArg.Type = TypeBoolean
	first.Return.MergeArgs[0] = 42

	second, ok := r.Lookup("CUSTOM")
	if !ok {
		t.Fatal("second Lookup CUSTOM: want found")
	}
	if second.Args[0].Type != TypeNumber {
		t.Fatalf("second Lookup Args[0].Type = %s; want immutable %s", second.Args[0].Type, TypeNumber)
	}
	if second.VarArg == nil || second.VarArg.Type != TypeString {
		t.Fatalf("second Lookup VarArg = %+v; want immutable TypeString", second.VarArg)
	}
	if second.Return.MergeArgs[0] != 0 {
		t.Fatalf("second Lookup Return.MergeArgs[0] = %d; want immutable 0", second.Return.MergeArgs[0])
	}
}

func mustRegister(t *testing.T, r *Registry, s FunctionSpec) {
	t.Helper()
	if err := r.Register(s); err != nil {
		t.Fatalf("Register(%q): %v", s.Name, err)
	}
}
