package inspect

import (
	"bytes"
	"strings"
	"testing"

	"github.com/docker/cli/templates"
	"gotest.tools/v3/assert"
	is "gotest.tools/v3/assert/cmp"
)

type testElement struct {
	DNS string `json:"Dns"`
}

func TestTemplateInspectorDefault(t *testing.T) {
	b := new(bytes.Buffer)
	tmpl, err := templates.Parse("{{.DNS}}")
	if err != nil {
		t.Fatal(err)
	}
	i := NewTemplateInspector(b, tmpl)
	if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
		t.Fatal(err)
	}

	if err := i.Flush(); err != nil {
		t.Fatal(err)
	}
	if b.String() != "0.0.0.0\n" {
		t.Fatalf("Expected `0.0.0.0\\n`, got `%s`", b.String())
	}
}

func TestTemplateInspectorEmpty(t *testing.T) {
	b := new(bytes.Buffer)
	tmpl, err := templates.Parse("{{.DNS}}")
	if err != nil {
		t.Fatal(err)
	}
	i := NewTemplateInspector(b, tmpl)

	if err := i.Flush(); err != nil {
		t.Fatal(err)
	}
	if b.String() != "\n" {
		t.Fatalf("Expected `\\n`, got `%s`", b.String())
	}
}

func TestTemplateInspectorTemplateError(t *testing.T) {
	b := new(bytes.Buffer)
	tmpl, err := templates.Parse("{{.Foo}}")
	if err != nil {
		t.Fatal(err)
	}
	i := NewTemplateInspector(b, tmpl)

	err = i.Inspect(testElement{"0.0.0.0"}, nil)
	if err == nil {
		t.Fatal("Expected error got nil")
	}

	if !strings.HasPrefix(err.Error(), "template parsing error") {
		t.Fatalf("Expected template error, got %v", err)
	}
}

func TestTemplateInspectorRawFallback(t *testing.T) {
	b := new(bytes.Buffer)
	tmpl, err := templates.Parse("{{.Dns}}")
	if err != nil {
		t.Fatal(err)
	}
	i := NewTemplateInspector(b, tmpl)
	if err := i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Dns": "0.0.0.0"}`)); err != nil {
		t.Fatal(err)
	}

	if err := i.Flush(); err != nil {
		t.Fatal(err)
	}
	if b.String() != "0.0.0.0\n" {
		t.Fatalf("Expected `0.0.0.0\\n`, got `%s`", b.String())
	}
}

func TestTemplateInspectorRawFallbackError(t *testing.T) {
	b := new(bytes.Buffer)
	tmpl, err := templates.Parse("{{.Dns}}")
	if err != nil {
		t.Fatal(err)
	}
	i := NewTemplateInspector(b, tmpl)
	err = i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Foo": "0.0.0.0"}`))
	if err == nil {
		t.Fatal("Expected error got nil")
	}

	if !strings.HasPrefix(err.Error(), "template parsing error") {
		t.Fatalf("Expected template error, got %v", err)
	}
}

func TestTemplateInspectorMultiple(t *testing.T) {
	b := new(bytes.Buffer)
	tmpl, err := templates.Parse("{{.DNS}}")
	if err != nil {
		t.Fatal(err)
	}
	i := NewTemplateInspector(b, tmpl)

	if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
		t.Fatal(err)
	}
	if err := i.Inspect(testElement{"1.1.1.1"}, nil); err != nil {
		t.Fatal(err)
	}

	if err := i.Flush(); err != nil {
		t.Fatal(err)
	}
	if b.String() != "0.0.0.0\n1.1.1.1\n" {
		t.Fatalf("Expected `0.0.0.0\\n1.1.1.1\\n`, got `%s`", b.String())
	}
}

func TestIndentedInspectorDefault(t *testing.T) {
	b := new(bytes.Buffer)
	i := NewIndentedInspector(b)
	if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
		t.Fatal(err)
	}

	if err := i.Flush(); err != nil {
		t.Fatal(err)
	}

	expected := `[
    {
        "Dns": "0.0.0.0"
    }
]
`
	if b.String() != expected {
		t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
	}
}

func TestIndentedInspectorMultiple(t *testing.T) {
	b := new(bytes.Buffer)
	i := NewIndentedInspector(b)
	if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
		t.Fatal(err)
	}

	if err := i.Inspect(testElement{"1.1.1.1"}, nil); err != nil {
		t.Fatal(err)
	}

	if err := i.Flush(); err != nil {
		t.Fatal(err)
	}

	expected := `[
    {
        "Dns": "0.0.0.0"
    },
    {
        "Dns": "1.1.1.1"
    }
]
`
	if b.String() != expected {
		t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
	}
}

func TestIndentedInspectorEmpty(t *testing.T) {
	b := new(bytes.Buffer)
	i := NewIndentedInspector(b)

	if err := i.Flush(); err != nil {
		t.Fatal(err)
	}

	expected := "[]\n"
	if b.String() != expected {
		t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
	}
}

func TestIndentedInspectorRawElements(t *testing.T) {
	b := new(bytes.Buffer)
	i := NewIndentedInspector(b)
	if err := i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Dns": "0.0.0.0", "Node": "0"}`)); err != nil {
		t.Fatal(err)
	}

	if err := i.Inspect(testElement{"1.1.1.1"}, []byte(`{"Dns": "1.1.1.1", "Node": "1"}`)); err != nil {
		t.Fatal(err)
	}

	if err := i.Flush(); err != nil {
		t.Fatal(err)
	}

	expected := `[
    {
        "Dns": "0.0.0.0",
        "Node": "0"
    },
    {
        "Dns": "1.1.1.1",
        "Node": "1"
    }
]
`
	if b.String() != expected {
		t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
	}
}

// moby/moby#32235
// This test verifies that even if `tryRawInspectFallback` is called the fields containing
// numerical values are displayed correctly.
// For example, `docker inspect --format "{{.Id}} {{.Size}} alpine` and
// `docker inspect --format "{{.ID}} {{.Size}} alpine" will have the same output which is
// sha256:651aa95985aa4a17a38ffcf71f598ec461924ca96865facc2c5782ef2d2be07f 3983636
func TestTemplateInspectorRawFallbackNumber(t *testing.T) {
	// Using typedElem to automatically fall to tryRawInspectFallback.
	typedElem := struct {
		ID string `json:"Id"`
	}{"ad3"}
	testcases := []struct {
		raw []byte
		exp string
	}{
		{raw: []byte(`{"Id": "ad3", "Size": 53317}`), exp: "53317 ad3\n"},
		{raw: []byte(`{"Id": "ad3", "Size": 53317.102}`), exp: "53317.102 ad3\n"},
		{raw: []byte(`{"Id": "ad3", "Size": 53317.0}`), exp: "53317.0 ad3\n"},
	}
	b := new(bytes.Buffer)
	tmpl, err := templates.Parse("{{.Size}} {{.Id}}")
	assert.NilError(t, err)

	i := NewTemplateInspector(b, tmpl)
	for _, tc := range testcases {
		err = i.Inspect(typedElem, tc.raw)
		assert.NilError(t, err)

		err = i.Flush()
		assert.NilError(t, err)

		assert.Check(t, is.Equal(tc.exp, b.String()))
		b.Reset()
	}
}

func TestNewTemplateInspectorFromString(t *testing.T) {
	testCases := []struct {
		name     string
		template string
		expected string
	}{
		{
			name:     "empty template outputs json by default",
			template: "",
			expected: `[
    {
        "Name": "test"
    }
]
`,
		},
		{
			name:     "json specific value outputs json",
			template: "json",
			expected: `[{"Name":"test"}]
`,
		},
		{
			name:     "template is applied",
			template: "{{.Name}}",
			expected: "test\n",
		},
	}
	value := struct {
		Name string
	}{
		"test",
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			b := new(bytes.Buffer)
			i, err := NewTemplateInspectorFromString(b, tc.template)
			assert.NilError(t, err)

			err = i.Inspect(value, nil)
			assert.NilError(t, err)

			err = i.Flush()
			assert.NilError(t, err)

			assert.Equal(t, b.String(), tc.expected)
		})
	}
}
