package yqlib

import (
	"io"
	"os"
	"testing"

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

func createTestFile(content string) string {
	tempFile, err := createTempFile()
	if err != nil {
		panic(err)
	}

	_, err = tempFile.Write([]byte(content))
	if err != nil {
		panic(err)
	}

	safelyCloseFile(tempFile)

	return tempFile.Name()
}

func readFile(filename string) string {
	bytes, err := os.ReadFile(filename)
	if err != nil {
		panic(err)
	}
	return string(bytes)
}

func TestFrontMatterSplitWithLeadingSep(t *testing.T) {
	file := createTestFile(`---
a: apple
b: banana
---
not a 
yaml: doc
`)

	expectedYamlFm := `---
a: apple
b: banana
`

	expectedContent := `---
not a 
yaml: doc
`

	fmHandler := NewFrontMatterHandler(file)
	err := fmHandler.Split()
	if err != nil {
		panic(err)
	}

	yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())

	test.AssertResult(t, expectedYamlFm, yamlFm)

	contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
	if err != nil {
		panic(err)
	}
	test.AssertResult(t, expectedContent, string(contentBytes))

	tryRemoveTempFile(file)
	fmHandler.CleanUp()
}

func TestFrontMatterSplitWithNoLeadingSep(t *testing.T) {
	file := createTestFile(`a: apple
b: banana
---
not a 
yaml: doc
`)

	expectedYamlFm := `a: apple
b: banana
`

	expectedContent := `---
not a 
yaml: doc
`

	fmHandler := NewFrontMatterHandler(file)
	err := fmHandler.Split()
	if err != nil {
		panic(err)
	}

	yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())

	test.AssertResult(t, expectedYamlFm, yamlFm)

	contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
	if err != nil {
		panic(err)
	}
	test.AssertResult(t, expectedContent, string(contentBytes))

	tryRemoveTempFile(file)
	fmHandler.CleanUp()
}

func TestFrontMatterFilenamePreserved(t *testing.T) {
	// Regression test for https://github.com/mikefarah/yq/issues/2538
	// When using --front-matter, the filename operator should return
	// the original filename, not the path to the temporary file.
	file := createTestFile(`---
name: john
---
Some content
`)
	originalFilename := "/path/to/original/file.md"

	fmHandler := NewFrontMatterHandler(file)
	err := fmHandler.Split()
	if err != nil {
		panic(err)
	}

	tempFilename := fmHandler.GetYamlFrontMatterFilename()

	// Register the alias (as the command code does)
	SetFilenameAlias(tempFilename, originalFilename)
	defer ClearFilenameAliases()

	// Verify resolveFilename returns the original name
	resolved := resolveFilename(tempFilename)
	test.AssertResult(t, originalFilename, resolved)

	// Read documents using the temp file, verify they get the original filename
	reader, cleanup, err := readStream(tempFilename)
	if err != nil {
		panic(err)
	}
	defer cleanup()
	decoder := NewYamlDecoder(ConfiguredYamlPreferences)
	docs, err := readDocuments(reader, tempFilename, 0, decoder)
	if err != nil {
		panic(err)
	}

	if docs.Len() == 0 {
		t.Fatal("expected at least one document")
	}

	firstDoc := docs.Front().Value.(*CandidateNode)
	test.AssertResult(t, originalFilename, firstDoc.filename)

	tryRemoveTempFile(file)
	fmHandler.CleanUp()
}

func TestFrontMatterSplitWithBOM(t *testing.T) {
	// Regression test for https://github.com/mikefarah/yq/issues/2496
	// A UTF-8 BOM before the opening --- must be skipped, otherwise the
	// separator isn't recognised and the opening --- is lost.
	file := createTestFile("\ufeff---\na: apple\nb: banana\n---\nnot a\nyaml: doc\n")

	expectedYamlFm := `---
a: apple
b: banana
`

	expectedContent := `---
not a
yaml: doc
`

	fmHandler := NewFrontMatterHandler(file)
	err := fmHandler.Split()
	if err != nil {
		panic(err)
	}

	yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())

	test.AssertResult(t, expectedYamlFm, yamlFm)

	contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
	if err != nil {
		panic(err)
	}
	test.AssertResult(t, expectedContent, string(contentBytes))

	tryRemoveTempFile(file)
	fmHandler.CleanUp()
}

func TestFrontMatterSplitWithBOMFromStdin(t *testing.T) {
	// Regression test for https://github.com/mikefarah/yq/issues/2496
	// A UTF-8 BOM must also be skipped when reading front matter from stdin.
	originalStdin := os.Stdin
	defer func() { os.Stdin = originalStdin }()

	r, w, err := os.Pipe()
	if err != nil {
		panic(err)
	}
	os.Stdin = r
	defer safelyCloseFile(r)

	go func() {
		_, writeErr := w.WriteString("\ufeff---\na: apple\nb: banana\n---\nnot a\nyaml: doc\n")
		if writeErr != nil {
			t.Errorf("failed to write front matter to the stdin pipe: %v", writeErr)
		}
		safelyCloseFile(w)
	}()

	expectedYamlFm := `---
a: apple
b: banana
`

	expectedContent := `---
not a
yaml: doc
`

	fmHandler := NewFrontMatterHandler("-")
	err = fmHandler.Split()
	if err != nil {
		panic(err)
	}

	yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())

	test.AssertResult(t, expectedYamlFm, yamlFm)

	contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
	if err != nil {
		panic(err)
	}
	test.AssertResult(t, expectedContent, string(contentBytes))

	fmHandler.CleanUp()
}

func TestFrontMatterSplitWithArray(t *testing.T) {
	file := createTestFile(`[1,2,3]
---
not a 
yaml: doc
`)

	expectedYamlFm := "[1,2,3]\n"

	expectedContent := `---
not a 
yaml: doc
`

	fmHandler := NewFrontMatterHandler(file)
	err := fmHandler.Split()
	if err != nil {
		panic(err)
	}

	yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())

	test.AssertResult(t, expectedYamlFm, yamlFm)

	contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
	if err != nil {
		panic(err)
	}
	test.AssertResult(t, expectedContent, string(contentBytes))

	tryRemoveTempFile(file)
	fmHandler.CleanUp()
}
