package yqlib

import (
	"bufio"
	"bytes"
	"container/list"
	"fmt"
	"io"
	"os"
	"strings"
	"testing"

	"github.com/mikefarah/yq/v4/test"
	yaml "gopkg.in/yaml.v3"
)

type expressionScenario struct {
	description           string
	subdescription        string
	environmentVariable   string
	document              string
	document2             string
	expression            string
	expected              []string
	skipDoc               bool
	dontFormatInputForDoc bool // dont format input doc for documentation generation
}

func testScenario(t *testing.T, s *expressionScenario) {
	var results *list.List
	var err error

	node, err := treeCreator.ParsePath(s.expression)
	if err != nil {
		t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
		return
	}
	inputs := list.New()

	if s.document != "" {
		inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0)
		if err != nil {
			t.Error(err, s.document, s.expression)
			return
		}
		if s.document2 != "" {
			moreInputs, err := readDocuments(strings.NewReader(s.document2), "another.yml", 1)
			if err != nil {
				t.Error(err, s.document, s.expression)
				return
			}
			inputs.PushBackList(moreInputs)
		}
	} else {
		candidateNode := &CandidateNode{
			Document:  0,
			Filename:  "",
			Node:      &yaml.Node{Tag: "!!null"},
			FileIndex: 0,
		}
		inputs.PushBack(candidateNode)

	}

	if s.environmentVariable != "" {
		os.Setenv("myenv", s.environmentVariable)
	}

	results, err = treeNavigator.GetMatchingNodes(inputs, node)

	if err != nil {
		t.Error(fmt.Errorf("%v: %v", err, s.expression))
		return
	}
	test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
}

func resultsToString(results *list.List) []string {
	var pretty []string = make([]string, 0)
	for el := results.Front(); el != nil; el = el.Next() {
		n := el.Value.(*CandidateNode)
		pretty = append(pretty, NodeToString(n))
	}
	return pretty
}

func writeOrPanic(w *bufio.Writer, text string) {
	_, err := w.WriteString(text)
	if err != nil {
		panic(err)
	}
}

func copyFromHeader(title string, out *os.File) error {
	source := fmt.Sprintf("doc/headers/%v.md", title)
	_, err := os.Stat(source)
	if os.IsNotExist(err) {
		return nil
	}
	in, err := os.Open(source) // nolint gosec
	if err != nil {
		return err
	}
	defer safelyCloseFile(in)
	_, err = io.Copy(out, in)
	return err
}

func formatYaml(yaml string, filename string) string {
	var output bytes.Buffer
	printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)

	node, err := treeCreator.ParsePath(".. style= \"\"")
	if err != nil {
		panic(err)
	}
	streamEvaluator := NewStreamEvaluator()
	err = streamEvaluator.Evaluate(filename, strings.NewReader(yaml), node, printer)
	if err != nil {
		panic(err)
	}
	return output.String()
}

func documentScenarios(t *testing.T, title string, scenarios []expressionScenario) {
	f, err := os.Create(fmt.Sprintf("doc/%v.md", title))

	if err != nil {
		t.Error(err)
		return
	}
	defer f.Close()

	err = copyFromHeader(title, f)
	if err != nil {
		t.Error(err)
		return
	}

	w := bufio.NewWriter(f)
	writeOrPanic(w, "\n")

	for _, s := range scenarios {
		if !s.skipDoc {
			documentScenario(t, w, s)
		}
	}
	w.Flush()
}

func documentScenario(t *testing.T, w *bufio.Writer, s expressionScenario) {
	writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))

	if s.subdescription != "" {
		writeOrPanic(w, s.subdescription)
		writeOrPanic(w, "\n\n")
	}

	formattedDoc, formattedDoc2 := documentInput(w, s)

	writeOrPanic(w, "will output\n")

	documentOutput(t, w, s, formattedDoc, formattedDoc2)
}

func documentInput(w *bufio.Writer, s expressionScenario) (string, string) {
	formattedDoc := ""
	formattedDoc2 := ""
	command := "eval"

	envCommand := ""

	if s.environmentVariable != "" {
		envCommand = fmt.Sprintf("myenv=\"%v\" ", s.environmentVariable)
		os.Setenv("myenv", s.environmentVariable)
	}

	if s.document != "" {
		if s.dontFormatInputForDoc {
			formattedDoc = s.document + "\n"
		} else {
			formattedDoc = formatYaml(s.document, "sample.yml")
		}

		writeOrPanic(w, "Given a sample.yml file of:\n")
		writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc))

		files := "sample.yml"

		if s.document2 != "" {
			if s.dontFormatInputForDoc {
				formattedDoc2 = s.document2 + "\n"
			} else {
				formattedDoc2 = formatYaml(s.document2, "another.yml")
			}

			writeOrPanic(w, "And another sample another.yml file of:\n")
			writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc2))
			files = "sample.yml another.yml"
			command = "eval-all"
		}

		writeOrPanic(w, "then\n")

		if s.expression != "" {
			writeOrPanic(w, fmt.Sprintf("```bash\n%vyq %v '%v' %v\n```\n", envCommand, command, s.expression, files))
		} else {
			writeOrPanic(w, fmt.Sprintf("```bash\n%vyq %v %v\n```\n", envCommand, command, files))
		}
	} else {
		writeOrPanic(w, "Running\n")
		writeOrPanic(w, fmt.Sprintf("```bash\n%vyq %v --null-input '%v'\n```\n", envCommand, command, s.expression))
	}
	return formattedDoc, formattedDoc2
}

func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formattedDoc string, formattedDoc2 string) {
	var output bytes.Buffer
	var err error
	printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)

	node, err := treeCreator.ParsePath(s.expression)
	if err != nil {
		t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
		return
	}

	inputs := list.New()

	if s.document != "" {
		inputs, err = readDocuments(strings.NewReader(formattedDoc), "sample.yml", 0)
		if err != nil {
			t.Error(err, s.document, s.expression)
			return
		}
		if s.document2 != "" {
			moreInputs, err := readDocuments(strings.NewReader(formattedDoc2), "another.yml", 1)
			if err != nil {
				t.Error(err, s.document, s.expression)
				return
			}
			inputs.PushBackList(moreInputs)
		}
	} else {
		candidateNode := &CandidateNode{
			Document:  0,
			Filename:  "",
			Node:      &yaml.Node{Tag: "!!null"},
			FileIndex: 0,
		}
		inputs.PushBack(candidateNode)

	}

	results, err := treeNavigator.GetMatchingNodes(inputs, node)
	if err != nil {
		t.Error(err, s.expression)
	}

	err = printer.PrintResults(results)
	if err != nil {
		t.Error(err, s.expression)
	}

	writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
}
