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

package json

import (
	"fmt"
	"strings"
	"testing"

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

func TestParse_nonObject(t *testing.T) {
	src := `true`
	file, diags := Parse([]byte(src), "")
	if len(diags) != 1 {
		t.Errorf("got %d diagnostics; want 1", len(diags))
	}
	if file == nil {
		t.Fatalf("got nil File; want actual file")
	}
	if file.Body == nil {
		t.Fatalf("got nil Body; want actual body")
	}
	if file.Body.(*body).val == nil {
		t.Errorf("got nil Body object; want placeholder object")
	}
}

func TestParseTemplate(t *testing.T) {
	src := `{"greeting": "hello ${\"world\"}"}`
	file, diags := Parse([]byte(src), "")
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on parse; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}
	if file == nil {
		t.Fatalf("got nil File; want actual file")
	}
	if file.Body == nil {
		t.Fatalf("got nil Body; want actual body")
	}
	attrs, diags := file.Body.JustAttributes()
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on decode; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}

	val, diags := attrs["greeting"].Expr.Value(&hcl.EvalContext{})
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on eval; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}

	if !val.RawEquals(cty.StringVal("hello world")) {
		t.Errorf("wrong result %#v; want %#v", val, cty.StringVal("hello world"))
	}
}

func TestParseTemplateUnwrap(t *testing.T) {
	src := `{"greeting": "${true}"}`
	file, diags := Parse([]byte(src), "")
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on parse; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}
	if file == nil {
		t.Fatalf("got nil File; want actual file")
	}
	if file.Body == nil {
		t.Fatalf("got nil Body; want actual body")
	}
	attrs, diags := file.Body.JustAttributes()
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on decode; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}

	val, diags := attrs["greeting"].Expr.Value(&hcl.EvalContext{})
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on eval; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}

	if !val.RawEquals(cty.True) {
		t.Errorf("wrong result %#v; want %#v", val, cty.True)
	}
}

func TestParse_malformed(t *testing.T) {
	src := `{
  "http_proxy_url: "http://xxxxxx",
}`
	file, diags := Parse([]byte(src), "")
	if got, want := len(diags), 2; got != want {
		t.Errorf("got %d diagnostics; want %d", got, want)
	}
	if err, want := diags.Error(), `Missing property value colon`; !strings.Contains(err, want) {
		t.Errorf("diags are %q, but should contain %q", err, want)
	}
	if file == nil {
		t.Errorf("got nil File; want actual file")
	}
}

func TestParseWithStartPos(t *testing.T) {
	src := `{
  "foo": {
    "bar": "baz"
  }
}`
	part := `{
    "bar": "baz"
  }`

	file, diags := Parse([]byte(src), "")
	partFile, partDiags := ParseWithStartPos([]byte(part), "", hcl.Pos{Byte: 0, Line: 2, Column: 10})
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on parse src; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}
	if len(partDiags) != 0 {
		t.Errorf("got %d diagnostics on parse part src; want 0", len(partDiags))
		for _, diag := range partDiags {
			t.Logf("- %s", diag.Error())
		}
	}

	if file == nil {
		t.Fatalf("got nil File; want actual file")
	}
	if file.Body == nil {
		t.Fatalf("got nil Body; want actual body")
	}
	if partFile == nil {
		t.Fatalf("got nil part File; want actual file")
	}
	if partFile.Body == nil {
		t.Fatalf("got nil part Body; want actual body")
	}

	content, diags := file.Body.Content(&hcl.BodySchema{
		Blocks: []hcl.BlockHeaderSchema{{Type: "foo"}},
	})
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on decode; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}
	attrs, diags := content.Blocks[0].Body.JustAttributes()
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on decode; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}
	srcRange := attrs["bar"].Expr.Range()

	partAttrs, diags := partFile.Body.JustAttributes()
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on decode; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}
	partRange := partAttrs["bar"].Expr.Range()

	if srcRange.String() != partRange.String() {
		t.Errorf("The two ranges did not match: src=%s, part=%s", srcRange, partRange)
	}
}

func TestParseExpression(t *testing.T) {
	tests := []struct {
		Input string
		Want  string
	}{
		{
			`"hello"`,
			`cty.StringVal("hello")`,
		},
		{
			`"hello ${noun}"`,
			`cty.StringVal("hello world")`,
		},
		{
			"true",
			"cty.True",
		},
		{
			"false",
			"cty.False",
		},
		{
			"1",
			"cty.NumberIntVal(1)",
		},
		{
			"{}",
			"cty.EmptyObjectVal",
		},
		{
			`{"foo":"bar","baz":1}`,
			`cty.ObjectVal(map[string]cty.Value{"baz":cty.NumberIntVal(1), "foo":cty.StringVal("bar")})`,
		},
		{
			"[]",
			"cty.EmptyTupleVal",
		},
		{
			`["1",2,3]`,
			`cty.TupleVal([]cty.Value{cty.StringVal("1"), cty.NumberIntVal(2), cty.NumberIntVal(3)})`,
		},
	}

	for _, test := range tests {
		t.Run(test.Input, func(t *testing.T) {
			expr, diags := ParseExpression([]byte(test.Input), "")
			if diags.HasErrors() {
				t.Errorf("got %d diagnostics; want 0", len(diags))
				for _, d := range diags {
					t.Logf("  - %s", d.Error())
				}
			}

			value, diags := expr.Value(&hcl.EvalContext{
				Variables: map[string]cty.Value{
					"noun": cty.StringVal("world"),
				},
			})
			if diags.HasErrors() {
				t.Errorf("got %d diagnostics on decode value; want 0", len(diags))
				for _, d := range diags {
					t.Logf("  - %s", d.Error())
				}
			}
			got := fmt.Sprintf("%#v", value)

			if got != test.Want {
				t.Errorf("got %s, but want %s", got, test.Want)
			}
		})
	}
}

func TestParseExpression_malformed(t *testing.T) {
	src := `invalid`
	expr, diags := ParseExpression([]byte(src), "")
	if got, want := len(diags), 1; got != want {
		t.Errorf("got %d diagnostics; want %d", got, want)
	}
	if err, want := diags.Error(), `Invalid JSON keyword`; !strings.Contains(err, want) {
		t.Errorf("diags are %q, but should contain %q", err, want)
	}
	if expr == nil {
		t.Errorf("got nil Expression; want actual expression")
	}
}

func TestParseExpressionWithStartPos(t *testing.T) {
	src := `{
  "foo": "bar"
}`
	part := `"bar"`

	file, diags := Parse([]byte(src), "")
	partExpr, partDiags := ParseExpressionWithStartPos([]byte(part), "", hcl.Pos{Byte: 0, Line: 2, Column: 10})
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on parse src; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}
	if len(partDiags) != 0 {
		t.Errorf("got %d diagnostics on parse part src; want 0", len(partDiags))
		for _, diag := range partDiags {
			t.Logf("- %s", diag.Error())
		}
	}

	if file == nil {
		t.Fatalf("got nil File; want actual file")
	}
	if file.Body == nil {
		t.Fatalf("got nil Body: want actual body")
	}
	if partExpr == nil {
		t.Fatalf("got nil Expression; want actual expression")
	}

	content, diags := file.Body.Content(&hcl.BodySchema{
		Attributes: []hcl.AttributeSchema{{Name: "foo"}},
	})
	if len(diags) != 0 {
		t.Errorf("got %d diagnostics on decode; want 0", len(diags))
		for _, diag := range diags {
			t.Logf("- %s", diag.Error())
		}
	}
	expr := content.Attributes["foo"].Expr

	if expr.Range().String() != partExpr.Range().String() {
		t.Errorf("The two ranges did not match: src=%s, part=%s", expr.Range(), partExpr.Range())
	}
}
