// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package typeexpr

import (
	"fmt"
	"testing"

	"github.com/zclconf/go-cty/cty"
)

func TestTypeConstraintType(t *testing.T) {
	tyVal1 := TypeConstraintVal(cty.String)
	tyVal2 := TypeConstraintVal(cty.String)
	tyVal3 := TypeConstraintVal(cty.Number)

	if !tyVal1.RawEquals(tyVal2) {
		t.Errorf("tyVal1 not equal to tyVal2\ntyVal1: %#v\ntyVal2: %#v", tyVal1, tyVal2)
	}
	if tyVal1.RawEquals(tyVal3) {
		t.Errorf("tyVal1 equal to tyVal2, but should not be\ntyVal1: %#v\ntyVal3: %#v", tyVal1, tyVal3)
	}

	if got, want := TypeConstraintFromVal(tyVal1), cty.String; !got.Equals(want) {
		t.Errorf("wrong type extracted from tyVal1\ngot:  %#v\nwant: %#v", got, want)
	}
	if got, want := TypeConstraintFromVal(tyVal3), cty.Number; !got.Equals(want) {
		t.Errorf("wrong type extracted from tyVal3\ngot:  %#v\nwant: %#v", got, want)
	}
}

func TestConvertFunc(t *testing.T) {
	// This is testing the convert function directly, skipping over the HCL
	// parsing and evaluation steps that would normally lead there. There is
	// another test in the "integrationtest" package called TestTypeConvertFunc
	// that exercises the full path to this function via the hclsyntax parser.

	tests := []struct {
		val, ty cty.Value
		want    cty.Value
		wantErr string
	}{
		// The goal here is not an exhaustive set of conversions, since that's
		// already covered in cty/convert, but rather exercising different
		// permutations of success and failure to make sure the function
		// handles all of the results in a reasonable way.
		{
			cty.StringVal("hello"),
			TypeConstraintVal(cty.String),
			cty.StringVal("hello"),
			``,
		},
		{
			cty.True,
			TypeConstraintVal(cty.String),
			cty.StringVal("true"),
			``,
		},
		{
			cty.StringVal("hello"),
			TypeConstraintVal(cty.Bool),
			cty.NilVal,
			`a bool is required`,
		},
		{
			cty.UnknownVal(cty.Bool),
			TypeConstraintVal(cty.Bool),
			cty.UnknownVal(cty.Bool),
			``,
		},
		{
			cty.DynamicVal,
			TypeConstraintVal(cty.Bool),
			cty.UnknownVal(cty.Bool),
			``,
		},
		{
			cty.NullVal(cty.Bool),
			TypeConstraintVal(cty.Bool),
			cty.NullVal(cty.Bool),
			``,
		},
		{
			cty.NullVal(cty.DynamicPseudoType),
			TypeConstraintVal(cty.Bool),
			cty.NullVal(cty.Bool),
			``,
		},
		{
			cty.StringVal("hello").Mark(1),
			TypeConstraintVal(cty.String),
			cty.StringVal("hello").Mark(1),
			``,
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%#v to %#v", test.val, test.ty), func(t *testing.T) {
			got, err := ConvertFunc.Call([]cty.Value{test.val, test.ty})

			if err != nil {
				if test.wantErr != "" {
					if got, want := err.Error(), test.wantErr; got != want {
						t.Errorf("wrong error\ngot:  %s\nwant: %s", got, want)
					}
				} else {
					t.Errorf("unexpected error\ngot:  %s\nwant: <nil>", err)
				}
				return
			}
			if test.wantErr != "" {
				t.Errorf("wrong error\ngot:  <nil>\nwant: %s", test.wantErr)
			}

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