package yqlib

import (
	"bufio"
	"bytes"
	"container/list"
	"strings"
	"testing"

	"github.com/mikefarah/yq/v4/test"
)

var multiDocSample = `a: banana
---
a: apple
---
a: coconut
`

var multiDocSampleLeadingExpected = `# go cats
---
a: banana
---
a: apple
---
# cool
a: coconut
`

func nodeToList(candidate *CandidateNode) *list.List {
	elMap := list.New()
	elMap.PushBack(candidate)
	return elMap
}

func TestPrinterMultipleDocsInSequenceOnly(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, true)

	inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}

	el := inputs.Front()
	sample1 := nodeToList(el.Value.(*CandidateNode))

	el = el.Next()
	sample2 := nodeToList(el.Value.(*CandidateNode))

	el = el.Next()
	sample3 := nodeToList(el.Value.(*CandidateNode))

	err = printer.PrintResults(sample1)
	if err != nil {
		panic(err)
	}

	err = printer.PrintResults(sample2)
	if err != nil {
		panic(err)
	}

	err = printer.PrintResults(sample3)
	if err != nil {
		panic(err)
	}

	writer.Flush()
	test.AssertResult(t, multiDocSample, output.String())
}

func TestPrinterMultipleDocsInSequenceWithLeadingContent(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, true)

	inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}

	el := inputs.Front()
	el.Value.(*CandidateNode).LeadingContent = "# go cats\n$yqDocSeparator$\n"
	sample1 := nodeToList(el.Value.(*CandidateNode))

	el = el.Next()
	el.Value.(*CandidateNode).LeadingContent = "$yqDocSeparator$\n"
	sample2 := nodeToList(el.Value.(*CandidateNode))

	el = el.Next()
	el.Value.(*CandidateNode).LeadingContent = "$yqDocSeparator$\n# cool\n"
	sample3 := nodeToList(el.Value.(*CandidateNode))

	err = printer.PrintResults(sample1)
	if err != nil {
		panic(err)
	}

	err = printer.PrintResults(sample2)
	if err != nil {
		panic(err)
	}

	err = printer.PrintResults(sample3)
	if err != nil {
		panic(err)
	}

	writer.Flush()

	test.AssertResult(t, multiDocSampleLeadingExpected, output.String())
}

func TestPrinterMultipleFilesInSequence(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, true)

	inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}

	el := inputs.Front()
	elNode := el.Value.(*CandidateNode)
	elNode.document = 0
	elNode.fileIndex = 0
	sample1 := nodeToList(elNode)

	el = el.Next()
	elNode = el.Value.(*CandidateNode)
	elNode.document = 0
	elNode.fileIndex = 1
	sample2 := nodeToList(elNode)

	el = el.Next()
	elNode = el.Value.(*CandidateNode)
	elNode.document = 0
	elNode.fileIndex = 2
	sample3 := nodeToList(elNode)

	err = printer.PrintResults(sample1)
	if err != nil {
		panic(err)
	}

	err = printer.PrintResults(sample2)
	if err != nil {
		panic(err)
	}

	err = printer.PrintResults(sample3)
	if err != nil {
		panic(err)
	}

	writer.Flush()
	test.AssertResult(t, multiDocSample, output.String())
}

func TestPrinterMultipleFilesInSequenceWithLeadingContent(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, true)

	inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}

	el := inputs.Front()
	elNode := el.Value.(*CandidateNode)
	elNode.document = 0
	elNode.fileIndex = 0
	elNode.LeadingContent = "# go cats\n$yqDocSeparator$\n"
	sample1 := nodeToList(elNode)

	el = el.Next()
	elNode = el.Value.(*CandidateNode)
	elNode.document = 0
	elNode.fileIndex = 1
	elNode.LeadingContent = "$yqDocSeparator$\n"
	sample2 := nodeToList(elNode)

	el = el.Next()
	elNode = el.Value.(*CandidateNode)
	elNode.document = 0
	elNode.fileIndex = 2
	elNode.LeadingContent = "$yqDocSeparator$\n# cool\n"
	sample3 := nodeToList(elNode)

	err = printer.PrintResults(sample1)
	if err != nil {
		panic(err)
	}

	err = printer.PrintResults(sample2)
	if err != nil {
		panic(err)
	}

	err = printer.PrintResults(sample3)
	if err != nil {
		panic(err)
	}

	writer.Flush()
	test.AssertResult(t, multiDocSampleLeadingExpected, output.String())
}

func TestPrinterMultipleDocsInSinglePrint(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, true)

	inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}

	err = printer.PrintResults(inputs)
	if err != nil {
		panic(err)
	}

	writer.Flush()
	test.AssertResult(t, multiDocSample, output.String())
}

func TestPrinterMultipleDocsInSinglePrintWithLeadingDoc(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, true)

	inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}

	inputs.Front().Value.(*CandidateNode).LeadingContent = "# go cats\n$yqDocSeparator$\n"

	err = printer.PrintResults(inputs)
	if err != nil {
		panic(err)
	}

	writer.Flush()
	expected := `# go cats
---
a: banana
---
a: apple
---
a: coconut
`
	test.AssertResult(t, expected, output.String())
}

func TestPrinterMultipleDocsInSinglePrintWithLeadingDocTrailing(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, true)

	inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}
	inputs.Front().Value.(*CandidateNode).LeadingContent = "$yqDocSeparator$\n"
	err = printer.PrintResults(inputs)
	if err != nil {
		panic(err)
	}

	writer.Flush()
	expected := `---
a: banana
---
a: apple
---
a: coconut
`
	test.AssertResult(t, expected, output.String())
}

func TestPrinterScalarWithLeadingCont(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, true)

	node, err := getExpressionParser().ParseExpression(".a")
	if err != nil {
		panic(err)
	}
	streamEvaluator := NewStreamEvaluator()
	_, err = streamEvaluator.Evaluate("sample", strings.NewReader(multiDocSample), node, printer, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}

	writer.Flush()
	expected := `banana
---
apple
---
coconut
`
	test.AssertResult(t, expected, output.String())
}

func TestPrinterMultipleDocsJson(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	// note printDocSeparators is true, it should still not print document separators
	// when outputting JSON.
	prefs := ConfiguredJSONPreferences.Copy()
	prefs.Indent = 0
	encoder := NewJSONEncoder(prefs)
	if encoder == nil {
		t.Skipf("no support for %s output format", "json")
	}
	printer := NewPrinter(encoder, NewSinglePrinterWriter(writer))

	inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}

	inputs.Front().Value.(*CandidateNode).LeadingContent = "# ignore this\n"

	err = printer.PrintResults(inputs)
	if err != nil {
		panic(err)
	}

	expected := `{"a":"banana"}
{"a":"apple"}
{"a":"coconut"}
`

	writer.Flush()
	test.AssertResult(t, expected, output.String())
}

func TestPrinterNulSeparator(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, false)
	printer.SetNulSepOutput(true)
	node, err := getExpressionParser().ParseExpression(".a")
	if err != nil {
		panic(err)
	}
	streamEvaluator := NewStreamEvaluator()
	_, err = streamEvaluator.Evaluate("sample", strings.NewReader(multiDocSample), node, printer, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}

	writer.Flush()
	expected := "banana\x00apple\x00coconut\x00"
	test.AssertResult(t, expected, output.String())
}

func TestPrinterNulSeparatorWithJson(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	// note printDocSeparators is true, it should still not print document separators
	// when outputting JSON.
	prefs := ConfiguredJSONPreferences.Copy()
	prefs.Indent = 0
	encoder := NewJSONEncoder(prefs)
	if encoder == nil {
		t.Skipf("no support for %s output format", "json")
	}
	printer := NewPrinter(encoder, NewSinglePrinterWriter(writer))
	printer.SetNulSepOutput(true)

	inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}

	inputs.Front().Value.(*CandidateNode).LeadingContent = "# ignore this\n"

	err = printer.PrintResults(inputs)
	if err != nil {
		panic(err)
	}

	expected := `{"a":"banana"}` + "\x00" + `{"a":"apple"}` + "\x00" + `{"a":"coconut"}` + "\x00"

	writer.Flush()
	test.AssertResult(t, expected, output.String())
}

func TestPrinterRootUnwrap(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, false)
	node, err := getExpressionParser().ParseExpression(".")
	if err != nil {
		panic(err)
	}
	streamEvaluator := NewStreamEvaluator()
	_, err = streamEvaluator.Evaluate("sample", strings.NewReader("'a'"), node, printer, NewYamlDecoder(ConfiguredYamlPreferences))
	if err != nil {
		panic(err)
	}

	writer.Flush()
	expected := `a
`
	test.AssertResult(t, expected, output.String())
}

func TestRemoveLastEOL(t *testing.T) {
	// Test with \r\n
	buffer := bytes.NewBufferString("test\r\n")
	removeLastEOL(buffer)
	test.AssertResult(t, "test", buffer.String())

	// Test with \n only
	buffer = bytes.NewBufferString("test\n")
	removeLastEOL(buffer)
	test.AssertResult(t, "test", buffer.String())

	// Test with \r only
	buffer = bytes.NewBufferString("test\r")
	removeLastEOL(buffer)
	test.AssertResult(t, "test", buffer.String())

	// Test with no EOL
	buffer = bytes.NewBufferString("test")
	removeLastEOL(buffer)
	test.AssertResult(t, "test", buffer.String())

	// Test with empty buffer
	buffer = bytes.NewBufferString("")
	removeLastEOL(buffer)
	test.AssertResult(t, "", buffer.String())

	// Test with multiple \r\n
	buffer = bytes.NewBufferString("line1\r\nline2\r\n")
	removeLastEOL(buffer)
	test.AssertResult(t, "line1\r\nline2", buffer.String())
}

func TestPrinterPrintedAnything(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, true)

	test.AssertResult(t, false, printer.PrintedAnything())

	// Print a scalar value
	node := createStringScalarNode("test")
	nodeList := nodeToList(node)
	err := printer.PrintResults(nodeList)
	if err != nil {
		t.Fatal(err)
	}

	// Should now be true
	test.AssertResult(t, true, printer.PrintedAnything())
}

func TestPrinterNulSeparatorWithNullChar(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, false)
	printer.SetNulSepOutput(true)

	// Create a node with null character
	node := createStringScalarNode("test\x00value")
	nodeList := nodeToList(node)

	err := printer.PrintResults(nodeList)
	if err == nil {
		t.Fatal("Expected error for null character in NUL separated output")
	}

	expectedError := "can't serialise value because it contains NUL char and you are using NUL separated output"
	if err.Error() != expectedError {
		t.Fatalf("Expected error '%s', got '%s'", expectedError, err.Error())
	}
}

func TestPrinterSetNulSepOutput(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, false)

	// Test setting NUL separator output
	printer.SetNulSepOutput(true)
	test.AssertResult(t, true, true) // Placeholder assertion

	printer.SetNulSepOutput(false)
	// Should also not cause errors
	test.AssertResult(t, false, false) // Placeholder assertion
}

func TestPrinterSetAppendix(t *testing.T) {
	var output bytes.Buffer
	var writer = bufio.NewWriter(&output)
	printer := NewSimpleYamlPrinter(writer, true, 2, true)

	// Test setting appendix
	appendix := strings.NewReader("appendix content")
	printer.SetAppendix(appendix)
	test.AssertResult(t, true, true) // Placeholder assertion
}
