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

package hcl

import (
	"bytes"
	"fmt"
	"testing"

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

func TestDiagnosticTextWriter(t *testing.T) {
	tests := []struct {
		Input *Diagnostic
		Want  string
	}{
		{
			&Diagnostic{
				Severity: DiagError,
				Summary:  "Splines not reticulated",
				Detail:   "All splines must be pre-reticulated.",
				Subject: &Range{
					Start: Pos{
						Byte:   0,
						Column: 1,
						Line:   1,
					},
					End: Pos{
						Byte:   3,
						Column: 4,
						Line:   1,
					},
				},
			},
			`Error: Splines not reticulated

  on  line 1, in hardcoded-context:
   1: foo = 1

All splines must be pre-reticulated.

`,
		},
		{
			&Diagnostic{
				Severity: DiagError,
				Summary:  "Unsupported attribute",
				Detail:   `"baz" is not a supported top-level attribute. Did you mean "bam"?`,
				Subject: &Range{
					Start: Pos{
						Byte:   16,
						Column: 1,
						Line:   3,
					},
					End: Pos{
						Byte:   19,
						Column: 4,
						Line:   3,
					},
				},
			},
			`Error: Unsupported attribute

  on  line 3, in hardcoded-context:
   3: baz = 3

"baz" is not a supported top-level
attribute. Did you mean "bam"?

`,
		},
		{
			&Diagnostic{
				Severity: DiagError,
				Summary:  "Unsupported attribute",
				Detail:   `"pizza" is not a supported attribute. Did you mean "pizzetta"?`,
				Subject: &Range{
					Start: Pos{
						Byte:   42,
						Column: 3,
						Line:   5,
					},
					End: Pos{
						Byte:   47,
						Column: 8,
						Line:   5,
					},
				},
				// This is actually not a great example of a context, but is here to test
				// whether we're able to show a multi-line context when needed.
				Context: &Range{
					Start: Pos{
						Byte:   24,
						Column: 1,
						Line:   4,
					},
					End: Pos{
						Byte:   60,
						Column: 2,
						Line:   6,
					},
				},
			},
			`Error: Unsupported attribute

  on  line 5, in hardcoded-context:
   4: block "party" {
   5:   pizza = "cheese"
   6: }

"pizza" is not a supported attribute.
Did you mean "pizzetta"?

`,
		},
		{
			&Diagnostic{
				Severity: DiagError,
				Summary:  "Test of including relevant variable values",
				Detail:   `This diagnostic includes an expression and an evalcontext.`,
				Subject: &Range{
					Start: Pos{
						Byte:   42,
						Column: 3,
						Line:   5,
					},
					End: Pos{
						Byte:   47,
						Column: 8,
						Line:   5,
					},
				},
				Expression: &diagnosticTestExpr{
					vars: []Traversal{
						{
							TraverseRoot{
								Name: "foo",
							},
						},
						{
							TraverseRoot{
								Name: "bar",
							},
							TraverseAttr{
								Name: "baz",
							},
						},
						{
							TraverseRoot{
								Name: "missing",
							},
						},
						{
							TraverseRoot{
								Name: "boz",
							},
						},
					},
				},
				EvalContext: &EvalContext{
					parent: &EvalContext{
						Variables: map[string]cty.Value{
							"foo": cty.StringVal("foo value"),
						},
					},
					Variables: map[string]cty.Value{
						"bar": cty.ObjectVal(map[string]cty.Value{
							"baz": cty.ListValEmpty(cty.String),
						}),
						"boz":    cty.NumberIntVal(5),
						"unused": cty.True,
					},
				},
			},
			`Error: Test of including relevant variable values

  on  line 5, in hardcoded-context:
   5:   pizza = "cheese"

with bar.baz as empty list of string,
     boz as 5,
     foo as "foo value".

This diagnostic includes an expression
and an evalcontext.

`,
		},
	}

	files := map[string]*File{
		"": &File{
			Bytes: []byte(testDiagnosticTextWriterSource),
			Nav:   &diagnosticTestNav{},
		},
	}

	for i, test := range tests {
		t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
			bwr := &bytes.Buffer{}
			dwr := NewDiagnosticTextWriter(bwr, files, 40, false)
			err := dwr.WriteDiagnostic(test.Input)
			if err != nil {
				t.Fatalf("unexpected error: %s", err)
			}
			got := bwr.String()
			if got != test.Want {
				t.Errorf("wrong result\n\ngot:\n%swant:\n%s", got, test.Want)
			}
		})
	}
}

const testDiagnosticTextWriterSource = `foo = 1
bar = 2
baz = 3
block "party" {
  pizza = "cheese"
}
`

type diagnosticTestNav struct {
}

func (tn *diagnosticTestNav) ContextString(offset int) string {
	return "hardcoded-context"
}

type diagnosticTestExpr struct {
	vars []Traversal
	staticExpr
}

func (e *diagnosticTestExpr) Variables() []Traversal {
	return e.vars
}
