package linter

import (
	"flag"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"regexp"
	"strings"
	"testing"

	jsonnet "github.com/google/go-jsonnet"
	"github.com/google/go-jsonnet/internal/testutils"
)

var update = flag.Bool("update", false, "update .golden files")

type linterTest struct {
	name   string
	input  string
	output string
}

type ChangedGoldensList struct {
	changedGoldens []string
}

func runTest(t *testing.T, test *linterTest, changedGoldensList *ChangedGoldensList) {
	read := func(file string) []byte {
		bytz, err := ioutil.ReadFile(file)
		if err != nil {
			t.Fatalf("reading file: %s: %v", file, err)
		}
		return bytz
	}

	input := read(test.input)

	vm := jsonnet.MakeVM()

	var outBuilder strings.Builder

	errorsFound := LintSnippet(vm, &outBuilder, []Snippet{Snippet{FileName: test.name, Code: string(input)}})

	outData := outBuilder.String()

	if outData == "" && errorsFound {
		t.Error(fmt.Errorf("return value indicates problems present, but no output was produced"))
	}

	if outData != "" && !errorsFound {
		t.Error(fmt.Errorf("return value indicates no problems, but output is not empty:\n%v", outData))
	}

	if *update {
		changed, err := testutils.UpdateGoldenFile(test.output, []byte(outData), 0666)
		if err != nil {
			t.Error(err)
		}
		if changed {
			changedGoldensList.changedGoldens = append(changedGoldensList.changedGoldens, test.output)
		}
	} else {
		golden, err := ioutil.ReadFile(test.output)
		if err != nil {
			t.Error(err)
			return
		}
		if diff, hasDiff := testutils.CompareWithGolden(outData, golden); hasDiff {
			t.Error(fmt.Errorf("golden file %v has diff:\n%v", test.input, diff))
		}
	}
}

func runTests(t *testing.T, tests []*linterTest) {
	read := func(file string) []byte {
		bytz, err := ioutil.ReadFile(file)
		if err != nil {
			t.Fatalf("reading file: %s: %v", file, err)
		}
		return bytz
	}

	vm := jsonnet.MakeVM()

	var snippets []Snippet

	for _, test := range tests {
		input := read(test.input)

		snippets = append(snippets, Snippet{FileName: test.name, Code: string(input)})
	}

	var outBuilder strings.Builder

	errorsFound := LintSnippet(vm, &outBuilder, snippets)

	outData := outBuilder.String()

	if outData == "" && errorsFound {
		t.Error(fmt.Errorf("return value indicates problems present, but no output was produced"))
	}

	if outData != "" && !errorsFound {
		t.Error(fmt.Errorf("return value indicates no problems, but output is not empty:\n%v", outData))
	}
}

func TestLinter(t *testing.T) {
	flag.Parse()

	var tests []*linterTest

	match, err := filepath.Glob("testdata/*.jsonnet")
	if err != nil {
		t.Fatal(err)
	}

	matchRegular, err := filepath.Glob("../testdata/*.jsonnet")
	if err != nil {
		t.Fatal(err)
	}
	match = append(match, matchRegular...)

	jsonnetExtRE := regexp.MustCompile(`\.jsonnet$`)

	for _, input := range match {
		// Skip escaped filenames.
		if strings.ContainsRune(input, '%') {
			continue
		}
		name := jsonnetExtRE.ReplaceAllString(input, "")
		golden := jsonnetExtRE.ReplaceAllString(input, ".linter.golden")
		tests = append(tests, &linterTest{
			name:   name,
			input:  input,
			output: golden,
		})
	}

	changedGoldensList := ChangedGoldensList{}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			runTest(t, test, &changedGoldensList)
		})
	}

	if *update {
		// Little hack: a failed test which pritns update stats.
		t.Run("Goldens Updated", func(t *testing.T) {
			t.Logf("Expected failure, for printing update stats. Does not appear without `-update`.")
			t.Logf("%d linter goldens updated:\n", len(changedGoldensList.changedGoldens))
			for _, golden := range changedGoldensList.changedGoldens {
				t.Log(golden)
			}
			t.Fail()
		})
	}

	t.Run("passing multiple input files", func(t *testing.T) {
		runTests(t, tests)
	})
}
