package userfunc

import (
	"fmt"
	"testing"

	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/hclsyntax"
	"github.com/zclconf/go-cty/cty"
)

func TestDecodeUserFunctions(t *testing.T) {
	tests := []struct {
		src       string
		testExpr  string
		baseCtx   *hcl.EvalContext
		want      cty.Value
		diagCount int
	}{
		{
			`
function "greet" {
  params = [name]
  result = "Hello, ${name}."
}
`,
			`greet("Ermintrude")`,
			nil,
			cty.StringVal("Hello, Ermintrude."),
			0,
		},
		{
			`
function "greet" {
  params = [name]
  result = "Hello, ${name}."
}
`,
			`greet()`,
			nil,
			cty.DynamicVal,
			1, // missing value for "name"
		},
		{
			`
function "greet" {
  params = [name]
  result = "Hello, ${name}."
}
`,
			`greet("Ermintrude", "extra")`,
			nil,
			cty.DynamicVal,
			1, // too many arguments
		},
		{
			`
function "add" {
  params = [a, b]
  result = a + b
}
`,
			`add(1, 5)`,
			nil,
			cty.NumberIntVal(6),
			0,
		},
		{
			`
function "argstuple" {
  params = []
  variadic_param = args
  result = args
}
`,
			`argstuple("a", true, 1)`,
			nil,
			cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.True, cty.NumberIntVal(1)}),
			0,
		},
		{
			`
function "missing_var" {
  params = []
  result = nonexist
}
`,
			`missing_var()`,
			nil,
			cty.DynamicVal,
			1, // no variable named "nonexist"
		},
		{
			`
function "closure" {
  params = []
  result = upvalue
}
`,
			`closure()`,
			&hcl.EvalContext{
				Variables: map[string]cty.Value{
					"upvalue": cty.True,
				},
			},
			cty.True,
			0,
		},
		{
			`
function "neg" {
  params = [val]
  result = -val
}
function "add" {
  params = [a, b]
  result = a + b
}
`,
			`neg(add(1, 3))`,
			nil,
			cty.NumberIntVal(-4),
			0,
		},
		{
			`
function "neg" {
  parrams = [val]
  result = -val
}
`,
			`null`,
			nil,
			cty.NullVal(cty.DynamicPseudoType),
			2, // missing attribute "params", and unknown attribute "parrams"
		},
	}

	for i, test := range tests {
		t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
			f, diags := hclsyntax.ParseConfig([]byte(test.src), "config", hcl.Pos{Line: 1, Column: 1})
			if f == nil || f.Body == nil {
				t.Fatalf("got nil file or body")
			}

			funcs, _, funcsDiags := decodeUserFunctions(f.Body, "function", func() *hcl.EvalContext {
				return test.baseCtx
			})
			diags = append(diags, funcsDiags...)

			expr, exprParseDiags := hclsyntax.ParseExpression([]byte(test.testExpr), "testexpr", hcl.Pos{Line: 1, Column: 1})
			diags = append(diags, exprParseDiags...)
			if expr == nil {
				t.Fatalf("parsing test expr returned nil")
			}

			got, exprDiags := expr.Value(&hcl.EvalContext{
				Functions: funcs,
			})
			diags = append(diags, exprDiags...)

			if len(diags) != test.diagCount {
				t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount)
				for _, diag := range diags {
					t.Logf("- %s", diag)
				}
			}

			if !got.RawEquals(test.want) {
				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.want)
			}
		})
	}
}
